Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Correct Code

jongman
July 28, 2014

Writing Correct Code

7월 28일 LG전자에서 한 세미나 슬라이드입니다.

링크가 살아 있는 PDF버전은 https://dl.dropboxusercontent.com/u/18062090/writing-correct-code-slides.pdf 여기에서 받으실 수 있습니다.

jongman

July 28, 2014
Tweet

More Decks by jongman

Other Decks in Technology

Transcript

  1. 자기소개 퀀트 개발자 : DRW Trading Group (~'09) GETCO (~'11)

    Two Sigma Investments ('14~) 알고리즘 문제 해결 전략 ('11) algospot.com 운영진 ('07~) 2 / 128
  2. 자기소개 프로그래밍 대회 참가 : 탑코더 오픈 준우승 ('07) 구글

    코드 잼 결선 ('04, '06, '08) ACM‑ICPC 결선 ('03, '04) 전문 분야 : 일단 일 벌리고 수습하느라 고통받기 모르는 것 잘 아는것처럼 얘기하기 3 / 128
  3. 오늘의 토픽 #1 다음 중 무엇이 옳은가 ? / /

    K & R s t y l e f o r ( i n t i = 0 ; i < n ; + + i ) { . . . } / / B S D s t y l e f o r ( i n t i = 0 ; i < n ; + + i ) { . . . } 5 / 128
  4. 오늘의 토픽 #2 변수명 , 무엇으로 해야 하는가 ? c

    o n s t i n t a n s w e r _ t o _ l i f e _ t h e _ u n i v e r s e _ a n d _ e v e r y t h i n g = 4 2 ; c o n s t i n t A n s w e r T o L i f e T h e U n i v e r s e A n d E v e r y t h i n g = 4 2 ; c o n s t i n t g A n s w e r T o L i f e T h e U n i v e r s e A n d E v e r y t h i n g = 4 2 ; c o n s t i n t A n s w e r _ T o _ L i f e _ T h e _ U n i v e r s e _ A n d _ E v e r y t h i n g = 4 2 ; c o n s t i n t A N S W E R _ T O _ L I F E _ T H E _ U N I V E R S E _ A N D _ E V E R Y T H I N G = 4 2 ; c o n s t i n t A T L T U A E = 4 2 ; c o n s t i n t S I X _ B Y _ N I N E = 4 2 ; 6 / 128
  5. 진짜로 할 이야기 " 어떻게 하면 잘 동작하는 코드를 쉽게

    짤 수 있을까 ?" 연습할 필요가 있긴 할까 ? 흔한 실수들은 어디에서 올까 ? 그 실수들은 어떻게 피 할까 ? 어떻게 하면 좋은 코드를 짤 수 있을까 ? 앞으로 어떻게 수련하면 될까 ? 10 / 128
  6. 주의 오늘 하는 얘기는 모두 저의 사견 여러분의 취향 ,

    상황 , 목표에 맞지 않을 수도 있음 ! 적절히 취사선택해 주세요 ^^; 11 / 128
  7. 왜 공부해야 하나요 ? 테스팅이 있잖아요 ! 대충 짜도 테스팅만

    꼼꼼하게 하면 잘 되는 것 아닌가 요 ? 12 / 128
  8. 범퍼카 프로그래밍 " 아이고 머리야 , 범퍼카라서 정말 다행이야 !

    이번엔 이쪽으로 돌려볼까 ?" " 어 , 이렇게 하니까 테스트가 실패하네 ? 이번엔 이렇게 고쳐보자 " 15 / 128
  9. 두 가지 문제 세상에 존재하는 모든 버그 : 모든 테스트를

    통과했다 ! 테스트는 코드에 대한 이해를 대체할 수 없다 범퍼카만 운전해서 주차 실력이 늘 수 있을까 ? " 범퍼카 운전 경력만 20 년입니다 걱정마세요 !" → ... 라고 말하는 발렛 파킹 요원이 있다면 ? 16 / 128
  10. 피터 : 테스트는 먹는 건가 ? d e f c

    r o s s ( A , B ) : " C r o s s p r o d u c t o f e l e m e n t s i n A a n d e l e m e n t s i n B . " r e t u r n [ a + b f o r a i n A f o r b i n B ] d i g i t s = ' 1 2 3 4 5 6 7 8 9 ' r o w s = ' A B C D E F G H I ' c o l s = d i g i t s s q u a r e s = c r o s s ( r o w s , c o l s ) u n i t l i s t = ( [ c r o s s ( r o w s , c ) f o r c i n c o l s ] + [ c r o s s ( r , c o l s ) f o r r i n r o w s ] + [ c r o s s ( r s , c s ) f o r r s i n ( ' A B C ' , ' D E F ' , ' G H I ' ) f o r c s i n ( ' 1 2 3 ' , ' 4 5 6 ' , ' 7 8 9 ' ) ] ) . . . 19 / 128
  11. 피터 : 테스트도 하지 뭐 ... d e f t

    e s t ( ) : " A s e t o f u n i t t e s t s . " a s s e r t l e n ( s q u a r e s ) = = 8 1 a s s e r t l e n ( u n i t l i s t ) = = 2 7 a s s e r t a l l ( l e n ( u n i t s [ s ] ) = = 3 f o r s i n s q u a r e s ) a s s e r t a l l ( l e n ( p e e r s [ s ] ) = = 2 0 f o r s i n s q u a r e s ) a s s e r t u n i t s [ ' C 2 ' ] = = [ [ ' A 2 ' , ' B 2 ' , ' C 2 ' , ' D 2 ' , ' E 2 ' , ' F 2 ' , ' G 2 ' , ' H 2 ' , [ ' C 1 ' , ' C 2 ' , ' C 3 ' , ' C 4 ' , ' C 5 ' , ' C 6 ' , ' C 7 ' , ' C 8 ' , [ ' A 1 ' , ' A 2 ' , ' A 3 ' , ' B 1 ' , ' B 2 ' , ' B 3 ' , ' C 1 ' , ' C 2 ' , a s s e r t p e e r s [ ' C 2 ' ] = = s e t ( [ ' A 2 ' , ' B 2 ' , ' D 2 ' , ' E 2 ' , ' F 2 ' , ' G 2 ' , ' H 2 ' , ' I ' C 1 ' , ' C 3 ' , ' C 4 ' , ' C 5 ' , ' C 6 ' , ' C 7 ' , ' C 8 ' , ' C ' A 1 ' , ' A 3 ' , ' B 1 ' , ' B 3 ' ] ) p r i n t ' A l l t e s t s p a s s . ' 20 / 128
  12. 론 : 자 봐 , 테스트를 시작했다 ! r e

    q u i r e ' t e s t / u n i t ' r e q u i r e ' s u d o k u t e s t . r b ' r e q u i r e ' p r o j e c t . r b ' c l a s s T C _ M y T e s t < T e s t : : U n i t : : T e s t C a s e d e f s e t u p e n d d e f t e s t _ h o o k u p a s s e r t _ e q u a l ( 5 , 2 + 2 ) e n d e n d 23 / 128
  13. c l a s s G a m e d

    e f G a m e : : t e s t _ g a m e g a m e = G a m e . n e w g a m e . t e s t _ g a m e g a m e e n d d e f t e s t _ g a m e @ c e l l s = [ ] f o r i i n 0 . . 8 0 @ c e l l s . p u s h i e n d e n d d e f c e l l ( i ) @ c e l l s [ i ] e n d e n d 론 : 게임 클래스도 만들어야지 ! 24 / 128
  14. 동작하는 기능 격자의 이 칸에 이 숫자를 넣읍시다 이 격자의

    이 칸에 넣을 수 있는 숫자들은 뭔가요 ? 들어갈 수 있는 숫자가 하나만 있는 칸이 있나요 ? 게임이 끝났나요 ? ( 끝 ) 27 / 128
  15. TDD 를 하지 말자는게 아닙니다 어디 가서 " 구모씨가 테스트해봐야

    소용없다더라 " 하 시면 안됩니다 다만 : 테스트는 만능이 아니다 코드와 알고리즘을 이해하지 못하면 테스트도 도와 줄 수 없다 이해하기 쉽고 , 간결한 코드를 짜기 위한 꾸준한 수련 ! 어떻게 하면 더 좋은 코드를 짤 수 있나 감시하는 매의 30 / 128
  16. 할 수 있는 실수가 너무 많다 ! ( 스택 |

    자료형 | 배열 크기 |...) 오버플로우 깨진 루프 불변식 잘못된 상수 요구 조건을 잘못 이해 라이브러리 잘못 사용하기 최소 크기의 입력 / 최대 크기의 입력 잘못 처리 답이 존재하지 않는 경우 잘못 구현 .... 32 / 128
  17. 어떻게 이들을 피해가야 할까 ? 원포인트 레슨 : 수많은 함정들을

    하나하나 공부해서 피해간다 방법론 : 함정들을 만나지 않을 수 있는 기본기를 갖춘 다 ( 현실 : 둘다 해야됨 ) 33 / 128
  18. 예제 문제 : LECTURE 길이 2n 인 소문자 문자열을 길이

    2 씩의 문자열로 분리한 뒤 , 정렬해 합쳐서 출력하라 . (n <= 500) abbaaccb ab ba ac cb ab < ac < ba < cb abacbacb 35 / 128
  19. / / c o d e # 1 : 1

    l i n e t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; / / 길이 2 인 문자열들로 분리 f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; f o r ( i n t i = 0 ; i < b i g r a m s . s i z e ( ) ; + + i ) { / / 삽입 정렬 i n t j = i - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } b i g r a m s [ j ] = t ; } s t r i n g r e t ; / / 정렬된 조각들을 결합 f o r ( c o n s t a u t o & s : b i g r a m s ) r e t + = s ; r e t u r n r e t ; } 36 / 128
  20. / / c o d e # 1 : 1

    l i n e t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; / / 길이 2 인 문자열들로 분리 f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; f o r ( i n t i = 0 ; i < b i g r a m s . s i z e ( ) ; + + i ) { / / 삽입 정렬 i n t j = i - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } b i g r a m s [ j ] = t ; / / 뭐가 문제? ? } s t r i n g r e t ; / / 정렬된 조각들을 결합 f o r ( c o n s t a u t o & s : b i g r a m s ) r e t + = s ; r e t u r n r e t ; } 37 / 128
  21. 삽입 정렬의 동작 s t r i n g t

    = b i g r a m s [ i ] ; 38 / 128
  22. 우리가 알고 있는 것 j 는 다음에 t 와 비교해야

    할 원소의 위치 j + 1 은 현재 t 가 있어야 할 자리 A [ j + 2 . . i ] 는 t 보다 큰 숫자들이 포함됨 이 사실들은 반복문의 진행 내내 성립한다 : → 반복문 불변식 ! 40 / 128
  23. 반복문 불변식 (Loop Invariant) 각 반복문 내용이 시작할 때 ,

    그리고 종료할 때 성립 중간 결과가 원하는 답으로 가는 길 위에 있나 ? / / ( 1 ) 여기서 성립한다 f o r ( . . . ; . . . ; . . . ) { / / ( 2 a ) 여기서 성립한다면 s o m e t h i n g ( ) ; s o m e t h i n g _ e l s e ( ) ; / / ( 2 b ) 여기서도 성립한다 } / / ( 3 ) 여기서 성립하면 → 우리가 원하는 결과 41 / 128
  24. 삽입 정렬과 반복문 불변식 i n t j = i

    - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } 1. b i g r a m s [ 0 . . j ] 는 정렬되어 있다 2. b i g r a m s [ j + 2 . . i ] 는 정렬되어 있고 , t 보다 크다 42 / 128
  25. / / c o d e # 1 : f

    i x e d s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; / / 길이 2 인 문자열들로 분리 f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; f o r ( i n t i = 0 ; i < b i g r a m s . s i z e ( ) ; + + i ) { / / 삽입 정렬 i n t j = i - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } b i g r a m s [ j + 1 ] = t ; } s t r i n g r e t ; / / 정렬된 조각들을 결합 f o r ( c o n s t a u t o & s : b i g r a m s ) r e t + = s ; r e t u r n r e t ; } 43 / 128
  26. 교훈 : 반복문 불변식 ! 반복문을 짜기 전에 반복문이 하는

    일과 내부의 변화 를 생각하기 ( 불변식을 주석으로 적어 두자 ) 44 / 128
  27. / / c o d e # 2 : 1

    l i n e t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < c o n s t c h a r * > s u f f i x ; / / 각 위치에서 시작하는 문자열 포인터 f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) / / 저장 s u f f i x . p u s h _ b a c k ( s . c _ s t r ( ) + i ) ; f o r ( i n t i = 0 ; i < s u f f i x . s i z e ( ) ; + + i ) / / 선택 정렬 f o r ( i n t j = i + 1 ; j < s u f f i x . s i z e ( ) ; + + j ) i f ( s t r n c m p ( s u f f i x [ i ] , s u f f i x [ j ] , 2 ) = = 1 ) / / 첫 2 글자만 비교하자 s w a p ( s u f f i x [ i ] , s u f f i x [ j ] ) ; c h a r b u f [ 1 0 0 1 ] ; f o r ( i n t i = 0 ; i < s . s i z e ( ) / 2 ; + + i ) / / 결과를 만든다 f o r ( i n t j = 0 ; j < 2 ; + + j ) b u f [ i * 2 + j ] = s u f f i x [ i ] [ j ] ; b u f [ s . s i z e ( ) ] = 0 ; r e t u r n s t r i n g ( b u f ) ; } 45 / 128
  28. / / c o d e # 2 : 1

    l i n e t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < c o n s t c h a r * > s u f f i x ; / / 각 위치에서 시작하는 문자열 포인터 f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) / / 저장 s u f f i x . p u s h _ b a c k ( s . c _ s t r ( ) + i ) ; f o r ( i n t i = 0 ; i < s u f f i x . s i z e ( ) ; + + i ) / / 선택 정렬 f o r ( i n t j = i + 1 ; j < s u f f i x . s i z e ( ) ; + + j ) i f ( s t r n c m p ( s u f f i x [ i ] , s u f f i x [ j ] , 2 ) = = 1 ) / / 첫 2 글자만 비교하자 s w a p ( s u f f i x [ i ] , s u f f i x [ j ] ) ; c h a r b u f [ 1 0 0 1 ] ; f o r ( i n t i = 0 ; i < s . s i z e ( ) / 2 ; + + i ) / / 결과를 만든다 f o r ( i n t j = 0 ; j < 2 ; + + j ) b u f [ i * 2 + j ] = s u f f i x [ i ] [ j ] ; b u f [ s . s i z e ( ) ] = 0 ; r e t u r n s t r i n g ( b u f ) ; } 46 / 128
  29. man strncmp RETURN VALUES The strcmp() and strncmp() functions return

    an integer greater than, equal to, or less than 0, according as the string s1 is greater than, equal to, or less than the string s2. The comparison is done using unsigned characters, so that '\200' is greater than '\0'. ‑1, 0, 1 외의 값도 반환된다 ! 47 / 128
  30. 교훈 : Know Your Tools! ( → C++ 은 이

    면에서는 헬게이트 !) 48 / 128
  31. C++ Pop Quiz (1) / / 일별 종가가 주어질 때

    하루 동안 일어난 최대 변화를 반환한다 i n t m a x _ p r i c e _ d i f f ( c o n s t v e c t o r < i n t > & p r i c e s ) { i n t r e t = 0 ; f o r ( i n t i = 0 ; i < p r i c e s . s i z e ( ) - 1 ; + + i ) r e t = m a x ( r e t , a b s ( p r i c e s [ i + 1 ] - p r i c e s [ i ] ) ) ; r e t u r n r e t ; } 뭐가 문제일까 ? 49 / 128
  32. C++ Pop Quiz (1) / / 일별 종가가 주어질 때

    하루 동안 일어난 최대 변화를 반환한다 i n t m a x _ p r i c e _ d i f f ( c o n s t v e c t o r < i n t > & p r i c e s ) { i n t r e t = 0 ; f o r ( i n t i = 0 ; i < p r i c e s . s i z e ( ) - 1 ; + + i ) r e t = m a x ( r e t , a b s ( p r i c e s [ i + 1 ] - p r i c e s [ i ] ) ) ; r e t u r n r e t ; } v e c t o r < i n t > e m p t y ; a s s e r t ( m a x _ p r i c e _ d i f f ( e m p t y ) = = 0 ) ; 런타임 오류 ! 50 / 128
  33. size_t f o r ( i n t i =

    0 ; i < p r i c e s . s i z e ( ) - 1 ; + + i ) Alias of one of the fundamental unsigned integer types. It is a type able to represent the size of any object in bytes: size_t is the type returned by the sizeof operator and is widely used in the standard library to represent sizes and counts. 64 비트 머신에서 대개 s i z e _ t 는 64 비트 부호 없는 정수 ! 51 / 128
  34. 대체 그럼 무슨 일이 ? A . s i z

    e ( ) = 0 A . s i z e ( ) - 1 = 2^64‑1 i < A . s i z e ( ) - 1 i 는 부호 있는 32 비트 정수 A . s i z e ( ) - 1 은 부호 없는 64 비트 정수 i 가 64 비트 부호 없는 정수로 캐스팅 ! 그런데 i 는 잘해봐야 32 비트 정수니까 비교는 항상 실패 ! 52 / 128
  35. C++ Pop Quiz (1) / / 방법1 f o r

    ( i n t i = 0 ; i < ( i n t ) p r i c e s . s i z e ( ) - 1 ; + + i ) r e t = m a x ( r e t , a b s ( p r i c e s [ i + 1 ] - p r i c e s [ i ] ) ) ; / / 방법2 f o r ( i n t i = 0 ; i + 1 < p r i c e s . s i z e ( ) ; + + i ) r e t = m a x ( r e t , a b s ( p r i c e s [ i + 1 ] - p r i c e s [ i ] ) ) ; / / 방법3 f o r ( i n t i = 1 ; i < p r i c e s . s i z e ( ) ; + + i ) r e t = m a x ( r e t , a b s ( p r i c e s [ i - 1 ] - p r i c e s [ i ] ) ) ; 53 / 128
  36. C++ Pop Quiz (2) / / 큰 정수 구현 s

    t r u c t B i g I n t e g e r { . . B i g I n t e g e r ( i n t x ) { . . } B i g I n t e g e r o p e r a t o r - ( c o n s t B i g I n t e g e r & r h s ) c o n s t { . . } B i g I n t e g e r o p e r a t o r ^ ( c o n s t B i g I n t e g e r & r h s ) c o n s t { . . } } ; o s t r e a m & o p e r a t o r < < ( o s t r e a m & o s , c o n s t B i g I n t e g e r & r h s ) { . . } i n t m a i n ( ) { / / l a r g e s t k n o w n m e r s e n n e p r i m e : 2 ^ 5 7 8 8 5 1 6 1 - 1 c o u t < < B i g I n t e g e r ( 2 ) ^ B i g I n t e g e r ( 5 7 8 8 5 1 6 1 ) - B i g I n t e g e r ( 1 ) < < e n d l ; } 컴파일 에러 ! 54 / 128
  37. 연산자 우선 순위 c o u t < < B

    i g I n t e g e r ( 2 ) ^ B i g I n t e g e r ( 5 7 8 8 5 1 6 1 ) - B i g I n t e g e r ( 1 ) < < e n d l ; 55 / 128
  38. C++ Pop Quiz (2): Fixed? / / 큰 정수 구현

    s t r u c t B i g I n t e g e r { . . B i g I n t e g e r ( i n t x ) { . . } B i g I n t e g e r o p e r a t o r - ( c o n s t B i g I n t e g e r & r h s ) c o n s t { . . } B i g I n t e g e r o p e r a t o r ^ ( c o n s t B i g I n t e g e r & r h s ) c o n s t { . . } } ; o s t r e a m & o p e r a t o r < < ( o s t r e a m & o s , c o n s t B i g I n t e g e r & r h s ) { . . } i n t m a i n ( ) { / / l a r g e s t k n o w n m e r s e n n e p r i m e : 2 ^ 5 7 8 8 5 1 6 1 - 1 c o u t < < ( B i g I n t e g e r ( 2 ) ^ B i g I n t e g e r ( 5 7 8 8 5 1 6 1 ) - B i g I n t e g e r ( 1 ) ) < < e n d l ; } 56 / 128
  39. c o u t < < ( B i g

    I n t e g e r ( 2 ) ^ B i g I n t e g e r ( 5 7 8 8 5 1 6 1 ) - B i g I n t e g e r ( 1 ) ) < < e n d l ; 57 / 128
  40. 연산자 우선 순위 c o u t < < (

    B i g I n t e g e r ( 2 ) ^ B i g I n t e g e r ( 5 7 8 8 5 1 6 1 ) ) - B i g I n t e g e r ( 1 ) < < e n d l ; Rule of thumb: ( 남이 ) 헷갈릴 가능성이 있다면 무조건 괄호로 감싸 라 ! 깔끔한 코드는 잠재적 오류만큼의 가치는 없다 ! 58 / 128
  41. C++ Pop Quiz (3) s t r u c t

    I t e m ; s t r u c t P l a y e r { v e c t o r < I t e m * > i t e m s ; i n t s t r e n g t h ; / / 들 수 있는 아이템의 최대 수는 힘과 같다 P l a y e r ( i n t s ) : s t r e n g t h ( s ) , i t e m s ( s t r e n g t h , n u l l p t r ) { } } ; 59 / 128
  42. C++ Pop Quiz (3) s t r u c t

    I t e m ; s t r u c t P l a y e r { v e c t o r < I t e m * > i t e m s ; i n t s t r e n g t h ; / / 들 수 있는 아이템의 최대 수는 힘과 같다 P l a y e r ( i n t s ) : s t r e n g t h ( s ) , i t e m s ( s t r e n g t h , n u l l p t r ) { } } ; v o i d t e s t _ p l a y e r ( ) { P l a y e r p ( 1 0 ) ; a s s e r t ( p . i t e m s . s i z e ( ) = = 1 0 ) ; / / 런타임 오류, 혹은 실패! } 60 / 128
  43. 초기화 리스트의 순서 s t r u c t P

    l a y e r { v e c t o r < I t e m * > i t e m s ; i n t s t r e n g t h ; / / 들 수 있는 아이템의 최대 수는 힘과 같다 P l a y e r ( i n t s ) : s t r e n g t h ( s ) , i t e m s ( s t r e n g t h , n u l l p t r ) { } 클래스 선언부 내의 순서대로 초기화됨 ! 초기화 리스트 내의 순서와 상관 없음 ! 61 / 128
  44. C++ Pop Quiz (3) s t r u c t

    P l a y e r { i n t s t r e n g t h ; v e c t o r < I t e m * > i t e m s ; / / 들 수 있는 아이템의 최대 수는 힘과 같다 P l a y e r ( i n t s ) : s t r e n g t h ( s ) , i t e m s ( s t r e n g t h , n u l l p t r ) { } 혹은 P l a y e r ( i n t s ) : s t r e n g t h ( s ) , i t e m s ( s , n u l l p t r ) { } 62 / 128
  45. C++ Pop Quiz (4) s t r u c t

    P e r s o n { s t r i n g n a m e ; v e c t o r < P e r s o n * > c h i l d r e n ; } ; v o i d c o l l e c t ( P e r s o n * p e r s o n , v e c t o r < P e r s o n * > & c o l l e c t e d ) { c o l l e c t e d . p u s h _ b a c k ( p e r s o n ) ; f o r ( a u t o c h i l d : p e r s o n - > c h i l d r e n ) c o l l e c t ( c h i l d , c o l l e c t e d ) ; } / / p e r s o n 과 그의 모든 자손을 배열에 모은다 v e c t o r < P e r s o n * > c o l l e c t _ f a m i l y _ r e c u r s i o n ( P e r s o n * p e r s o n ) { v e c t o r < P e r s o n * > c o l l e c t e d ; c o l l e c t ( p e r s o n , c o l l e c t e d ) ; r e t u r n c o l l e c t e d ; } 63 / 128
  46. 재귀호출이 싫어요 ! s t r u c t P

    e r s o n { s t r i n g n a m e ; v e c t o r < P e r s o n * > c h i l d r e n ; } ; / / p e r s o n 과 그의 모든 자손을 배열에 모은다 v e c t o r < P e r s o n * > c o l l e c t _ f a m i l y ( P e r s o n * p e r s o n ) { v e c t o r < P e r s o n * > c o l l e c t e d ( 1 , p e r s o n ) ; f o r ( a u t o p : c o l l e c t e d ) f o r ( a u t o c h i l d : p - > c h i l d r e n ) c o l l e c t e d . p u s h _ b a c k ( c h i l d ) ; r e t u r n c o l l e c t e d ; } 65 / 128
  47. 파헤치자 f o r ( a u t o p

    : c o l l e c t e d ) { . . } f o r ( a u t o i t = b e g i n ( c o l l e c t e d ) ; i t ! = e n d ( c o l l e c t e d ) ; + + i t ) { P e r s o n * p = * i t ; . . } f o r ( a u t o i t = c o l l e c t e d . b e g i n ( ) ; i t ! = c o l l e c t e d . e n d ( ) ; + + i t ) { P e r s o n * p = * i t ; . . } 66 / 128
  48. 반복자 무효화 v e c t o r 에 원소를

    추가하거나 , 지우거나 , 크기를 변경 하면 이미 존재하는 모든 반복자는 무효화될 수 있다 i t 값을 참조하면 런타임 오류가 발생할 수 있음 ! 종류에 따라 다르지만 다른 모든 컨테이너에도 반복자 무효화 규칙이 존재 See Iterator invalidation rules 67 / 128
  49. 무효화의 이유 v e c t o r 는 여유분을

    포함한 적당히 큰 메모리를 미리 할당해 둔다 꽉 찼을 때 p u s h _ b a c k 이 들어오면 : 더 큰 메모리를 할당해서 현재 메모리 내용을 복사 ! 이전 메모리는 해제한다 그러면 기존 반복자는 해제된 메모리를 가리킴 ! 68 / 128
  50. 개선 가능하면 안하는게 좋습니다 s t r u c t

    P e r s o n { s t r i n g n a m e ; v e c t o r < P e r s o n * > c h i l d r e n ; } ; / / p e r s o n 과 그의 모든 자손을 배열에 모은다 v e c t o r < P e r s o n * > c o l l e c t _ f a m i l y ( P e r s o n * p e r s o n ) { v e c t o r < P e r s o n * > c o l l e c t e d ( 1 , p e r s o n ) ; f o r ( i n t i = 0 ; i < c o l l e c t e d . s i z e ( ) ; + + i ) f o r ( a u t o c h i l d : c o l l e c t e d [ i ] - > c h i l d r e n ) c o l l e c t e d . p u s h _ b a c k ( c h i l d ) ; r e t u r n c o l l e c t e d ; } 69 / 128
  51. / / c o d e # 3 : 2

    l i n e s t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { i n t n = s . s i z e ( ) ; c h a r * * b i g r a m s = ( c h a r * * ) m a l l o c ( s i z e o f ( c h a r * ) * n / 2 ) ; f o r ( i n t i = 0 ; i < n / 2 ; + + i ) { b i g r a m s [ i ] = ( c h a r * ) m a l l o c ( s i z e o f ( c h a r ) * 2 ) ; s t r n c p y ( b i g r a m s [ i ] , s . c _ s t r ( ) + i * 2 , 2 ) ; } f o r ( i n t i = 0 ; i < n / 2 ; + + i ) f o r ( i n t j = i + 1 ; j < n / 2 ; + + j ) i f ( s t r n c m p ( b i g r a m s [ i ] , b i g r a m s [ j ] , 2 ) > 0 ) s w a p ( b i g r a m s [ i ] , b i g r a m s [ j ] ) ; c h a r * b u f = ( c h a r * ) m a l l o c ( s i z e o f ( c h a r ) * 1 0 0 0 ) ; f o r ( i n t i = 0 ; i < n / 2 ; + + i ) f o r ( i n t j = 0 ; j < 2 ; + + j ) b u f [ i * 2 + j ] = b i g r a m s [ i ] [ j ] ; b u f [ n ] = 0 ; r e t u r n s t r i n g ( b u f ) ; } 71 / 128
  52. 문제 : 배열 크기 ! C 문자열은 NULL 로 끝나는

    배열이므로 , 실제 길이보 다 용량이 1 커야 한다 c h a r * b u f = ( c h a r * ) m a l l o c ( s i z e o f ( c h a r ) * 1 0 0 0 ) ; 아래 줄은 왜 괜찮을까 ? b i g r a m s [ i ] = ( c h a r * ) m a l l o c ( s i z e o f ( c h a r ) * 2 ) ; 72 / 128
  53. / / c o d e # 3 : 2

    l i n e s t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { i n t n = s . s i z e ( ) ; c h a r * * b i g r a m s = ( c h a r * * ) m a l l o c ( s i z e o f ( c h a r * ) * n / 2 ) ; f o r ( i n t i = 0 ; i < n / 2 ; + + i ) { b i g r a m s [ i ] = ( c h a r * ) m a l l o c ( s i z e o f ( c h a r ) * 2 ) ; s t r n c p y ( b i g r a m s [ i ] , s . c _ s t r ( ) + i * 2 , 2 ) ; } f o r ( i n t i = 0 ; i < n / 2 ; + + i ) f o r ( i n t j = i + 1 ; j < n / 2 ; + + j ) i f ( s t r n c m p ( b i g r a m s [ i ] , b i g r a m s [ j ] , 2 ) > 0 ) s w a p ( b i g r a m s [ i ] , b i g r a m s [ j ] ) ; c h a r * b u f = ( c h a r * ) m a l l o c ( s i z e o f ( c h a r ) * 1 0 0 1 ) ; f o r ( i n t i = 0 ; i < n / 2 ; + + i ) f o r ( i n t j = 0 ; j < 2 ; + + j ) b u f [ i * 2 + j ] = b i g r a m s [ i ] [ j ] ; b u f [ n ] = 0 ; r e t u r n s t r i n g ( b u f ) ; } 73 / 128
  54. C 문자열이 복잡하다고 ? " 옛 선현들은 다 이걸로 코딩했어

    복잡하긴 뭘 " " 진정한 개발자는 바이트 단위에서 노는거야 C++ 문 자열은 똥이야 ! 똥이라고 " "80 년대생들이 K&R C 를 알겠냐 ? 요즘 애들은 로망이 없어요 " 75 / 128
  55. 쉽다 vs 어렵다 : 접근성 쉽다 : 내가 이미 알고

    있다 이미 내 컴퓨터에 깔려 있다 어렵다 : 학습 커브를 다시 통과해야 한다 고생해서 구해서 설치해야 한다 77 / 128
  56. 간결하다 vs 복잡하다 : 논리적 간결하다 : 한번에 한 가지의

    일을 한다 문맥에 신경쓰지 않아도 된다 복잡하다 : 여러 개의 일이 섞여 있다 문맥에 신경써야 한다 78 / 128
  57. 왜 간결함을 선호해야 하는가 ? 프로그래밍은 저글링과 같다 신경써야 하는

    모든 것들이 하나의 공 ! " 이 공을 던지고 싶나 ? 이 공은 사실 두개란다 " 공 하나의 가격은 비싸다 ! 나는 2 개의 공을 던지고 받을 수 있다 세계 기록 : 고작 13 개 ! 79 / 128
  58. C 스타일 문자열은 왜 복잡한가 ? 추가적인 문맥을 달고 온다

    ! 문자열의 최대 길이는 배열의 길이 ‑1 을 넘어갈 수 없다 strncpy 는 NULL 문자를 추가하기도 하고 , 하지 않 기도 한다 ! strncmp 만 사용하면 NULL 문자가 필요없다 ! 문자열을 사용하는 입장에서 이 문맥은 노이즈일 뿐 ! 80 / 128
  59. 추상화와 간결한 코드 문맥을 제거함으로써 더 높은 레벨에서 사고할 수

    있 게 해준다 ! 어떤 도구를 어떻게 사용해야 하는가 ? Part II 에서 계속 83 / 128
  60. / / c o d e # 4 : 1

    l i n e t o f i x s t r i n g s o r t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; + + i ) { i n t j = i - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } b i g r a m s [ j + 1 ] = t ; } s t r i n g r e t ; f o r ( c o n s t a u t o & s : b i g r a m s ) r e t + = s ; r e t u r n r e t ; } 84 / 128
  61. s t r i n g s o r t

    _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; + + i ) { i n t j = i - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } b i g r a m s [ j + 1 ] = t ; } s t r i n g r e t ; f o r ( c o n s t a u t o & s : b i g r a m s ) r e t + = s ; r e t u r n r e t ; } 85 / 128
  62. s t r i n g s o r t

    _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; f o r ( i n t i = 0 ; i < b i g r a m s . s i z e ( ) ; + + i ) { i n t j = i - 1 ; s t r i n g t = b i g r a m s [ i ] ; w h i l e ( j > = 0 & & b i g r a m s [ j ] > t ) { b i g r a m s [ j + 1 ] = b i g r a m s [ j ] ; - - j ; } b i g r a m s [ j + 1 ] = t ; } s t r i n g r e t ; f o r ( c o n s t a u t o & s : b i g r a m s ) r e t + = s ; r e t u r n r e t ; } 86 / 128
  63. 정줄놓 앞에 장사 없다 어떠한 원칙과 기법도 신중하고 주의깊은 프로그래머

    의 대체재가 될 수 없다 ! 은총알을 항상 경계하자 ! 88 / 128
  64. 이 외의 중요한 오류 클래스들 물리적 기계의 한계에서 오는 문제

    변수형 오버플로우 스택 오버플로우 실수 연산의 정확성 문제 ( 여기도 지뢰밭 ) http://floating‑point‑gui.de/ TopCoder Tutorial Part 1, Part 2 89 / 128
  65. Disclaimer 이 파트는 더더욱 주관적인 취향의 영역 모두에게 자신의 기준이

    있음 오늘의 이야기도 우주적 기준이 아님 미시적 관점 (X) 좋은 소프트웨어 아키텍처 , 디자인 패턴 , 소프 트웨어 스택 (O) 어떻게 간결한 함수를 작성할까 ? 어떻게 이 클 래스를 잘 만들까 ? 92 / 128
  66. 간결성 revisited 한 번에 하나의 일만 하기 문맥의 수로 결정된다

    혹이 달려오는 도구는 쓰지 말자 다양한 단계에서의 간결성 도구의 선택 함수 하나 클래스 구조 설계 93 / 128
  67. 같은 값이면 추상화 ! 복잡 간결 i n t A

    [ M A X _ N ] ; v e c t o r < i n t > A ; / / 정렬한 상태로 유지한다 i n t A [ M A X _ N ] ; s e t < i n t > A ; / / 모든 P e r s o n 에 대해 P e r s o n p e o p l e [ M A X _ N ] ; f o r ( i n t i = 0 ; i < n ; + + i ) { f o r ( a u t o p e r s o n : p e o p l e ) / / 최대값을 구한다 f o r ( i n t i = 0 ; i < n ; + + i ) { . . } * m a x _ e l e m e n t ( b e g i n ( A ) , e n d ( A ) ) / / 두 P e r s o n 을 비교 s t r u c t C o m p a r a t o r { . . } ; [ ] ( c o n s t P e r s o n & a , c o n s t P e r s o n & b ) { . . } i n t x [ N ] , y [ N ] , z [ N ] ; v e c t o r < P o i n t > p o i n t s ; 94 / 128
  68. 지옥에서 온 코드 .cpp s t r i n g

    c o m p l e x i t y _ h e l l ( c o n s t s t r i n g & s ) { c h a r b u f [ 1 0 2 4 ] , t m p [ 1 0 2 4 ] ; s t r c p y ( b u f , s . c _ s t r ( ) ) ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) f o r ( i n t j = i + 2 ; j < s . s i z e ( ) ; j + = 2 ) { i f ( b u f [ i ] > b u f [ j ] | | ( b u f [ i ] = = b u f [ j ] & & b u f [ i + 1 ] > b u f [ j + 1 ] ) ) { s t r n c p y ( t m p , & b u f [ i ] , 2 ) ; s t r n c p y ( & b u f [ i ] , & b u f [ j ] , 2 ) ; s t r n c p y ( & b u f [ j ] , t m p , 2 ) ; } } r e t u r n s t r i n g ( b u f ) ; } 95 / 128
  69. 개선 s t r i n g s o r

    t _ b i g r a m s ( c o n s t s t r i n g & s ) { v e c t o r < s t r i n g > b i g r a m s ; f o r ( i n t i = 0 ; i < s . s i z e ( ) ; i + = 2 ) b i g r a m s . p u s h _ b a c k ( s . s u b s t r ( i , 2 ) ) ; s o r t ( b i g r a m s . b e g i n ( ) , b i g r a m s . e n d ( ) ) ; s t r i n g r e t ; f o r ( c o n s t a u t o & b : b i g r a m s ) r e t + = b ; r e t u r n r e t ; } 97 / 128
  70. Mutable vs Immutable C 의 문자열 : 문자를 바꾸고 ,

    길이를 늘리고 , 줄일 수 있다 ! c h a r b u f [ 6 4 ] ; s t r c p y ( b u f , " H e l l o W o r l d ! " ) ; b u f [ 4 ] = ' ' ; Python 의 문자열 : 새 문자열을 얻는다 ! h e l l o = ' H e l l o W o r l d ! ' h e l l = h e l l o [ : 4 ] + ' ' + h e l l o [ 5 : ] 98 / 128
  71. 변수의 개념 Immutable: 특정 값에 이름을 붙인다 조작하면 새로운 값을

    얻는다 Mutable: 값을 담을 수 있는 메모리 " 조각 " 에 이름을 붙인다 메모리에 새 값을 써넣으면 모든 사람들의 값이 바 뀐다 ! 99 / 128
  72. Immutable 프로그래밍 모든 함수는 " 이전의 값을 이용해 새 값을

    계산한 다 " 의 연속 이전의 값이 바뀌면서 이전 부분과 다음 부분이 섞일 여지를 주지 않는다 파이프라인 ‑ 지향 프로그래밍 101 / 128
  73. 고려할 점 : 오버헤드 데이터를 생성하고 바로 처리하면 더 빠를

    수는 있다 ! 대부분의 경우 주의깊은 알고리즘과 자료 구조 선택으 로 극복 지원되는 경우에는 lazy evaluation 을 적극 활용 102 / 128
  74. 보다 고수준에서의 간결성 SRP (Single Responsibility Principle) 객체간의 직접적 의존성을

    최대한 줄인다 남아 있는 직접적 의존성 ‑ 추상화하라 ! 필요한 문맥만 남기고 없앤다 간결하다 ≠ 컴포넌트의 개수가 적다 가능한 상태의 수가 적다 Stateless 시스템을 향해 ! 103 / 128
  75. 예 : 온라인 채점 시스템 1. 새 소스 코드가 제출됨

    2. 해당 문제가 채점 준비된 상태인지 확인 3. 파일서버에서 채점 데이터 다운로드 4. 샌드박스 생성해서 프로그램 컴파일 및 실행 5. 채점 결과 확인 104 / 128
  76. 대체 뭐가 문제야 ? 엄청 많은 직접적 의존성 ! Actor

    들은 모두 상태를 잔뜩 쥐고 있다 문제들 : 재시도는 어떻게 하지 ? 한 Actor 가 실패하면 전체 시스템이 멈춘다 Actor 간 리소스 공유 107 / 128
  77. 왜 더 나은가 ? 컴포넌트도 , 의존성도 없다 각 작업에는

    아무런 상태가 없다 한 작업 실패가 다른 작업 실패에 영향을 주지 않음 투명한 재시도 정책 각 액터 단위로 관리하지 않고 태스크 큐 자체에서 관리 109 / 128
  78. 요점 정리 문맥의 수를 줄이는 것이 최우선 적절한 도구 선택

    전후 코드간 의존성 줄이기 : " 파이프라인 " 직접적 의존성 줄이기 추상화 의존성 만들기 110 / 128
  79. 이런 코드를 보셨나요 ? s t r u c t

    P l a y e r { s t r i n g n a m e ; } ; s t r u c t T e a m { v e c t o r < P l a y e r > m e m b e r s ; } ; / / 팀 내에 주어진 이름을 가진 사람이 있는가? b o o l h a s _ m e m b e r ( T e a m * t e a m , c o n s t s t r i n g & n a m e ) { b o o l f o u n d = f a l s e ; i f ( t e a m ! = n u l l p t r ) { i n t i ; f o r ( i = 0 ; i < t e a m - > m e m b e r s . s i z e ( ) ; + + i ) { i f ( t e a m - > m e m b e r s [ i ] . n a m e = = n a m e ) { b r e a k ; } } f o u n d = i < t e a m - > m e m b e r s . s i z e ( ) ; } r e t u r n f o u n d ; } 111 / 128
  80. 이런 코드를 보셨나요 ? / / A 에서 세 원소를

    골라 합이 s 가 되도록 할 수 있나? b o o l f i n d 3 ( c o n s t v e c t o r < i n t > & A , i n t s ) { b o o l f o u n d = f a l s e ; f o r ( i n t i = 0 ; i < A . s i z e ( ) ; + + i ) { f o r ( i n t j = i + 1 ; j < A . s i z e ( ) ; + + j ) { f o r ( i n t k = j + 1 ; k < A . s i z e ( ) ; + + k ) { i f ( A [ i ] + A [ j ] + A [ k ] = = s ) { f o u n d = t r u e ; b r e a k ; } } i f ( f o u n d ) b r e a k ; } i f ( f o u n d ) b r e a k ; } r e t u r n f o u n d ; 112 / 128
  81. 이런 코드를 보셨나요 ? s t r u c t

    P l a y e r { s t r i n g n a m e ; i n t s t r , d e x , m a n a ; } ; b o o l o p e r a t o r < ( c o n s t P l a y e r & a , c o n s t P l a y e r & b ) { i f ( a . s t r = = b . s t r ) { i f ( a . d e x = = b . d e x ) { i f ( a . m a n a = = b . m a n a ) { r e t u r n a . n a m e < b . n a m e ; } r e t u r n a . m a n a < b . m a n a ; } r e t u r n a . d e x < b . d e x ; } r e t u r n a . s t r < b . s t r ; } 113 / 128
  82. 깊은 중첩 (nesting) if 안에 for 안에 if 안에 for..

    답을 찾았는지 기록하기 위한 북키핑 변수들 루프 밖에서 선언된 카운터 114 / 128
  83. 다시 짜기 s t r u c t P l

    a y e r { s t r i n g n a m e ; } ; s t r u c t T e a m { v e c t o r < P l a y e r > m e m b e r s ; } ; / / 팀 내에 주어진 이름을 가진 사람이 있는가? b o o l h a s _ m e m b e r _ r e w r i t e ( T e a m * t e a m , c o n s t s t r i n g & n a m e ) { i f ( ! t e a m ) r e t u r n f a l s e ; f o r ( c o n s t a u t o & m e m b e r : t e a m - > m e m b e r s ) i f ( m e m b e r . n a m e = = n a m e ) r e t u r n t r u e ; r e t u r n f a l s e ; } 115 / 128
  84. 다시 짜기 / / A 에서 세 원소를 골라 합이

    s 가 되도록 할 수 있나? b o o l f i n d 3 _ r e w r i t e ( c o n s t v e c t o r < i n t > & A , i n t s ) { f o r ( i n t i = 0 ; i < A . s i z e ( ) ; + + i ) f o r ( i n t j = i + 1 ; j < A . s i z e ( ) ; + + j ) f o r ( i n t k = j + 1 ; k < A . s i z e ( ) ; + + k ) i f ( A [ i ] + A [ j ] + A [ k ] = = s ) r e t u r n t r u e ; r e t u r n f a l s e ; } 116 / 128
  85. 다시 짜기 s t r u c t P l

    a y e r { s t r i n g n a m e ; i n t s t r , d e x , m a n a ; } ; b o o l o p e r a t o r < ( c o n s t P l a y e r & a , c o n s t P l a y e r & b ) { i f ( a . s t r ! = b . s t r ) r e t u r n a . s t r < b . s t r ; i f ( a . d e x ! = b . d e x ) r e t u r n a . d e x < b . d e x ; i f ( a . m a n a ! = b . m a n a ) r e t u r n a . m a n a < b . m a n a ; r e t u r n a . n a m e < b . n a m e ; } 117 / 128
  86. Return early " 하나의 return 문 ": 파스칼 시절의 유물

    언제 반환할까 ? 예외 상황 발생 반환값이 정해짐 재귀호출시의 기저사례 코드를 첫줄에서 아래줄까지 쭉 읽을 수 있게 한다 각 중첩은 새로운 문맥을 만든다 ! 118 / 128
  87. Design by Contract Design by contract (DbC), also known as

    contract programming, programming by contract and design‑by‑contract programming, is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. Wikipedia 119 / 128
  88. 머릿속에서 계약하기 머릿속에서 , 혹은 주석으로 : 이 함수는 무엇을

    가정할까 ? 이 함수의 결과는 무엇일까 ? 머릿속 시나리오 쓰려면 함수가 간결해야 한다 ! 함수의 처음과 끝부터 작성하기 120 / 128
  89. 선언적 프로그래밍 " 무엇을 "/" 어떻게 " 분리하기 SQL/regexp 의

    생산성 ! 직접 다 만들어 쓸 수는 없어도 유용한 생각 도구 ! 121 / 128
  90. map d e f d o u b l e

    _ e l e m e n t s ( A ) : B = [ ] f o r i i n x r a n g e ( l e n ( A ) ) : B . a p p e n d ( A [ i ] * 2 ) r e t u r n B d e f d o u b l e _ e l e m e n t s 2 ( A ) : r e t u r n m a p ( l a m b d a x : x * 2 , A ) 122 / 128
  91. 수련의 방법 짧은 코드 여러개 짜기 하비 프로젝트 온라인 채점

    시스템 , 프로젝트 오일러 실패 기록하기 , 포스트모템 다양한 수준의 추상화 , 도구를 사용해 보기 다양한 프로그래밍 패러다임 124 / 128
  92. Talk Inspired By: 생각하는 프로그래밍 ( 인사이트 ) Beautiful Code

    ( 한빛미디어 ) http://iq0.com/notes/deep.nesting.html Rich Hickey Simple Made Easy Value of Values 127 / 128