준비 됐나요? 좋습니다. 저기 빨간 점 보이나요? 왜 빨간색일까요? 위험해서라고요? 마음에 드네요. "이봐! 나를 쿠바로 망명시켜줘." 그래요. 이 레이저 때문에 공항에서 잡혀간 적도 있었지요. 거기에는 "DANGER" 이라고 써 붙여 있었어요. 그나저나 왜 빨간색 일까요? 나는 초록색 레이저도 있습니다. 모두 레이저 몇 개 정도는 있어야 해요. 나는 항상 세 개는 들고 다닙니다. 그 중 하나가 빨간색인데요. 가방에 배터리와 함께 들고 다닙니다. ... 이 빨간색 레이저는 12달러면 살 수 있습니다. 빨간색 레이저 보이지요? 멋지지요? 여기 초록색도 있습니다. 이 것도 좋아요. 아주 밝지요. 특별할 건 없지만 그래도 아주 밝아요. 이 걸 살짝 개조할 수도 있습니다. 레지스터 하나를 교체하면 출력을 0.25와트로 높일 수가 있는데요. 그러면 풍선 정도는 터트릴 수 있습니다. 멋지지요. 펑! 하지만 내가 제일 좋아하는 레이저는 이거입니다. 왜냐하면 저기 보이나요? 작은 보라색 점. 저기 위에 보여요? 어디 봅시다. 오! 밝게 잘 보이네요. 여기 바로 위에요. 아주 밝게 잘 보입니다. 하지만 저기에 비추면 잘 보이지 않아요. 보이나요? 잘 안보입니다. 여기에는 보이나요? 아니군요. 음. 잘 안보여요. 그런데 이런 걸 발견했어요. 그냥 사인펜입니다. 레이저를 쏴도 특별히 달라지진 않네요. 하지만 뚜껑을 보세요. 노란색이네요! 파란색이 아닙니다. 저기 오랜지 색 부분 보이나요? 잘 보세요. 조심스럽게 겨냥해야 합니다. 오! 오랜지 색이에요! 이건 어떤 레이저일까요? 이 건 조금 다른 레이저입니다. 내 안경은 햇빛을 받으면 렌즈가 짙어지는데요 잘 보일지 모르겠지만 렌즈들이 모두 까매졌습니다. 그렇지요? 여러분들이 안보이네요. 이건 자외선 레이저입니다. 하지만 자외선 레이저라고 팔지는 않아요. 아마존에서 17달러면 살 수 있습니다. 보라색 레이저라고 팔고 있지만 거짓말입니다. 보라색 레이저 보다 더 멋진 거에요. 자외선 레이저입니다. (역주: Ultra Violet Laser, Violet Laser) 누구나 자외선 레이저 하나쯤은 있어야 해요. 레이저 포인터로는 쓸모가 없지만 안경에 작은 그림들을 그릴 수는 있어요. "안경에 이름을 썼어!" 우야당간...... 저기 코드를 보세요. 어떤 코드인지 아는 사람있나요? 안경 렌즈가 까매서 볼 수 가 없네요. 어떤 코드인지 알겠나요? 저건 PDP-8의 소스 코드입니다. 1970년대의 소스코드는 저렇게 생겼었습니다. 내가 20살 즈음에 저런 코드를 작성했었지요. 20대 초반에도 그랬습니다. 저 소스 코드를 보세요. 어떤 의미인지 아는 사람 있나요? 프로그램이 메모리에 로딩될 주소입니다. 당시에는 소스 코드에 프로그램이 로딩될 주소를 명시했었습니다. 이 프로그램은 메모리 주소 200에 로딩될 겁니다. 그리고 데이터는 300에 저장됩니다. 어떤가요? 당연한거에요. 여러분은 프로그램이 어느 주소에 로딩될지 알아야 합니다. 여러분 말고 또 누가 그걸 결정하겠어요? 프로그래머가 메모리를 컨트롤 해야지요. 이대로는 아무런 문제가 없습니다. 다른 그림들도 보여드릴게요. 어디 봅시다. 이게 아니네요. 이 그림 말고 다른 게 있습니다. 요즘은 문서를 열면 같이 열렸던 문서들이 모두 같이 열려버려요. 저장할 필요 없고요. 이게 찾던 그림니다. 여러분이 이런 개발 언어를 사용한다고 생각해보세요. 그리고 이런 프로그램을 작성하는 거지요. 이 프로그램은 여기에 위치합니다. 주소 200에서 시작하지요. 여기 다른 사람이 작성한 서브루틴 라이브러리가 있습니다. 그나저나 그 당시에는 서브루틴 라이브러리가 드물었지요. 필요한 서브루틴은 직접 작성하곤 했습니다. 몇 몇 사람이 아주 유용한 서브루틴을 작성한 후에야 사람들은 자신의 프로그램에 그 것들을 넣기 시작했습니다. "그냥 프로그램에 추가해서 같이 컴파일 합시다." 보통 그런 방식으로 했지요. 서브루틴의 소스 코드를 가져와서 내 코드와 합치는 거죠. 그럼 어떤 문제가 있을까요? 지금 우린 1970년대를 이야기하고 있습니다. 그 당시 프로그램은 종이 테이프에 저장되었습니다. 종이 테이프는 초당 50문자 정도를 저장했습니다. 운이 좋은 경우에요. 그래서 소스 코드가 길어지면 컴파일 타임이 몇 분씩이나 길어지곤 했습니다. 그러다 보면 서브루틴 라이브러리가 너무 길어서 같이 컴파일 하지 못하는 경우도 생깁니다. 그런 경우는 서브루틴 라이브러리를 따로 컴파일 해서 메모리 주소 1200에 로딩합니다. 메모리 주소 1200에 컴파일된 바이너리 파일을 로딩하는 겁니다. 메인 프로그램은 메모리 주소 200에 로딩합니다. 그리고 심볼 정보를 담은 작은 파일이 있어요. 심볼을 보고 각 서브 루틴들이 어디에 로딩되어 있는지를 알 수 있습니다. 예를 들어 Get 서브루틴이 205번지에 있고 Put 서브루틴이 210번지에 있다는 것을 알 수 있는 거지요. 심볼 정보들은 프로그램과 함께 컴파일이 됩니다. 서브루틴 라이브러리는 따로 로딩됩니다. 그리곤 잘 동작하겠지요. 그럼 무엇이 문제일까요? 프로그램 크기가 변하지 않는 경우가 있을까요? 프로그램은 항상 커집니다! 계속 커지다 보면...... 어디 봅시다. 여기 다른 그림이 있어요. 네 이겁니다. 자! 메인 프로그램이 아주 커졌어요. 서브루틴을 덮어 써버렸네요. 정상적으로 동작하지 않습니다. 메인 프로그램이 서브루틴을 덮어써버렸지만 메인 프로그램은 그걸 모릅니다. 메인 프로그램이 1205번지를 호출하면 서브루틴이 아닌 자기의 어딘가를 실행하게 됩니다. 개발자도 어떤 상황인지 몰라요. 한참 디버깅 한 뒤에야 깨닫습니다. "이런, 프로그램이 너무 커져버렸네!" 그럼 어떻게 해결해야 할까요? 여러분들은 프로그래머자나요? 잘 생각해보세요. 서브루틴 라이브러리를 건너 뛰는 겁니다. 서브루틴들을 피해서 메인 프로그램을 배치하고 실행하는 거지요. 그런데 서브루틴 라이브러리도 점점 커집니다. 그러고 나면...... 이 그림이 맞는지 봅시다. 이건가? 네 맞습니다. 그러고 나면 이런 문제가 생깁니다. 실제 현장에서 발생하던 문제들입니다. 이런 문제들을 해결해야만 했었습니다. 어떻게 해결했는지 알겠나요? 우리는 재배치할 수 있는 코드를 생각해냈습니다. "이봐, 프로그램을 절대 주소 값에 로딩하는 건 너무 문제가 많아." "코드에 프로그램이 로딩될 위치를 넣지 않고 컴파일 하는 방법이 필요해." "로더에게는 로딩할 위치를 따로 알려주는 거야." 그러려면 또 다른 이슈가 있습니다. 바이너리 파일이 순수한 바이너리가 아니게되는 것 입니다. 어떤 숫자는 주소가 아니라 오프셋이라고 말해주는 정보가 바이너리에 추가되기 때문이지요. 로더는 오프셋으로 표시된 모든 주소에 시작 주소를 더해야만 합니다. 결과적으로 재배치 가능한 로더는 잘 동작했습니다. 재배치 가능한 바이너리와 로더를 개발했습니다. 하지만 새로운 문제가 생겼습니다. 서브루틴이 어디에 있는지를 알아야만 했지요. 서브루틴이 임의로 배치된다면 어떻게 로딩된 위치를 알 수 있을까요? 결국 그 정보도 바이너리 파일에 추가되어야 합니다. 처음에는 순수한 바이너리였던 파일이 점점 괴상한 파일이 되어 버립니다. 파일안에 온갖 잡다한 것들이 들어가있지요. 이제 바이너리 파일 안에는 서브루틴들의 이름이 들어가야 합니다. 그리고 각 서브루틴이 어디에 로딩될지도 알려줘야 하지요. 그리고 로더는 각 서브루틴을 어디에 로딩했는지 기억해야 합니다. 그런 후에 프로그램에는 또 필요한 것이 있습니다. 그건 이렇게 말하겠지요. "이봐, 난 이 프로그램들이 어디에 로딩될지 알아야겠어." "그리고 로더는 프로그램이 사용하는 서브루틴들을 링크해야 해." 그 당시의 컴퓨터는 느렸습니다. 디스크도 매우 느렸지요. 메모리도 많지 않았습니다. 디스크도 운이 좋으면 몇 MB 정도 가질 수 있었지요. 디스크의 탐색 속도도 매우 느렸습니다. 그래서 링크 시간이 아주 오래 걸렸지요. 라이브러리가 늘어나면서 점점 더 심해졌습니다. 프로그램이 더 많아지고 커지면서 링크하는데에 한 시간도 걸렸습니다. 여기 링크에 한 시간이나 걸리는 사람이 있나요? 여기 80년대에서 일하는 사람이 있어요? 이런! 지금도 그렇게 오래 걸린다고요? 그럼 C++ 프로그래머겠군요. 거기 셔츠에 써있어요. OSLO C++ User Group 이라고요. 링크 시간이 너무 길어지는 문제는 70년대에도 있었습니다. 그때 프로그램은 훨씬 작았었지만요. 해결책은 로딩 중에 링크 단계를 제거하는 거였습니다. 링크는 컴파일에 포함되어 두 번째 단계로 수행됐습니다. 컴파일이 끝나면 모든 링크 정보가 포함된 재배치 가능한 파일이 만들어 졌습니다. 결과적으로 프로그램 실행 시점에 로딩이 가능해졌습니다. 속도도 상대적으로 빨랐습니다. 그 후 여러 해 동안 이 방식이 유지되었습니다. 먼저 컴파일 해서 바이너리 파일을 만들고 바이너리 파일들을 링크해서 실행파일을 만듭니다. 그런 후에 실행 파일은 로딩할 수 있게 됩니다. 90년대까지 그런 방식을 유지했어요. 그러다 90년대에 변화가 생겼습니다. 무어의 법칙입니다. 무어의 법칙이 무엇이지요? 프로세서는 18개월 마다 두 배씩 빨라진다는 것 입니다. 1970년대부터 적용해봅시다. 당시의 프로세서는 0.5 MIPS 정도의 처리 능력을 가졌습니다. 그런 게 1990년대까지 진행되었지요. 그럼 20년이면 18개월이 몇 번이나 지난 것 이지요? 18개월이 몇 번 이지요? 대략 15번 정도네요. 그럼 2의 15승 이네요. 어디봅시다. 2의 15승이면 얼마나 되지요? 대략 32000 정도 향상된 거네요! 실제로도 대략 비슷합니다. 0.5MHz에서 2.8GHz로 증가했으니까요. 음 90년대에는 1GHz 정도였겠지요. 당시에는 디스크의 성능도 좋아졌습니다. 디스크의 회전 속도가 빨라졌습니다. 플래터에 저장되는 데이터도 훨씬 많아져서 헤드가 움직이는 거리도 짧아졌지요. 용량이 수백MB나 되는 디스크도 나왔습니다. 누군가 아주 좋은 아이디어를 냈습니다. 아주 좋은 아이디어에요. "이제 링크를 따로 할 필요가 없겠는걸." "로딩할때 같이 링크해도 되겠네." ActiveX를 기억하나요? OLE는요? DLL이 무엇의 약어이지요? Dynamically Linked Library. 로딩될 때에 링크된다는 뜻 입니다. 링크 단계가 다시 로더에게 넘어갔습니다. 그렇게 지금까지 이어져왔습니다. 여기 닷넷 프로그래머 있나요? 오! 아주 많군요. 자바 프로그래머 손들어 보세요. 얼마 없네요. 어떻게 된 거지요? 어떻게 닷넷 개발자만 있나요? 아... 이 컨퍼런스가 닷넷 관련된 행사 인가요? 닷넷에는 DLL이 있습니다. Dynamically Linked Libraries. 자바에는 Jar 파일이 있습니다. Jar 파일도 결국 DLL입니다. 같은 의도로 만들어 졌지요. C++는...... MS 환경에서 작업을 하면 DLL을 사용합니다. UNIX 계열 환경에서 작업을 하면 Shared Library가 있는데요 이 것도 역시 DLL과 동일합니다. 지금도 동적으로 링크하는 방식이 일반적인데요 이런 과정들을 거쳐 현재까지 왔습니다. DLL을 몇 개나 가지고 있나요? 비주얼 스튜디오에서 작업하는 사람들 솔루션에 프로젝트가 몇 개나 있나요? 60개? 그다지 나쁘지 않네요. 60개 보다 많은 사람? 있군요. 200개 이상 되는 사람? 왜 그렇게 하는지 아나요? 왜 소스 코드를 쪼개서 DLL들에 분산시켜 놓는지 아나요? 질문을 다르게 해보겠습니다. 어플리케이션을 배포할 때에 모든 DLL을 모아서 한 묶음으로 배포하나요? 그렇다면 왜 동적으로 링크하지요? 정적으로 링크하세요. 왜 굳이 동적으로 링크하지요? 모든 DLL을 모아서 하나의 거대한 덩어리로 만들고 그 덩어리를 디렉터리에 저장한 후에, "이게 우리 시스템이야" 동적으로 배포 안 하는데 왜 동적으로 링크하지요? 왜 동적 라이브러리가 생겨났지요? 동적 라이브러리는 동적으로 배포하기 위해서 만들어진 겁니다. 왜지요? 네트워크 속도는 조금 있다 이야기 하겠습니다. 잠시만요. 네트워크 속도로 인한 변화는 이야기할게 아주 많습니다. 90년대에 한 고객이 있었습니다. 실행 파일 크기가 250MB 정도였는데요 그 당시에는 아주 큰 프로그램이었습니다. 지금은 아니지만요. 그 당시 250MB는 아주 큰 용량이었습니다. CD 한 장에 들어가지도 않았지요. CAD 프로그램이었지요. 포드 같은 회사에 납품했었습니다. 포드는 이 프로그램으로 기어나 레버 등을 디자인했습니다. 그 고객은 이 프로그램을 정적으로 컴파일 했지요. 배포하기 위해 CD 여러장이 필요했습니다. 소스 코드 한 줄이 수정되면 다시 컴파일 하고 링크한 후에 모든 CD를 다시 구워서 고개들에게 전달해야 했습니다. 얼마나 많은 비용이 들었을지 상상이 갈 겁니다. 나는 90년대 중반에 거기에 갔었어요. 그들을 만났습니다. 당시에 그들은 실행파일을 여러 개의 DLL로 쪼개려고 하고 있었는데요. 왜냐하면 여러 개의 DLL로 실행파일을 분리해 놓으면 소스 코드가 한 줄 바뀌었을 때에 관련된 DLL 하나만 배포하면 되기 때문이지요. 이메일로 보낼 수 도 있었습니다. 그 당시에는 그것도 큰 일 이었지요. 그 당시에 이메일로는 250MB 파일을 보낼 수 없었습니다. 요즘에야 어렵지 않지만 당시에는 250MB를 이메일로 보내는 건 불가능했습니다. 하지만 100KB 크기의 DLL을 보내는건 가능했지요. 그 고객에겐 아주 바람직한 것이었습니다. 몇 달을 노력을 해서 어플리케이션을 잘게 쪼개서 여러 개의 DLL로 만들었지요. 그런 후에 치명적인 실수를 깨달았습니다. 치명적인 실수는 시스템을 임의의 DLL들로 쪼개는 것이 아무런 도움이 안 된다는 것 입니다. DLL들이 서로 서로 의존하는 상황이라면요. DLL들이 서로 의존하고 있는 상황이라면...... 여기 스캇 마이어의 세미나에 들어갔던 사람 있나요? 한 시간 전에 그가 한 연설이요. 열쇠구멍 문제에 대한 이야기였지요. 열쇠구멍 문제라는 것은 아무런 이유 없이 임의로 제약을 주는 것을 이야기합니다. 그냥 임의로 사람들을 구속하는 겁니다. GUI를 예로 들면 너무 작은 텍스트 박스를 본 적이 있지요? 텍스트 박스에 내용을 많이 적었는데 크기를 조절 할 수 없는 경우 말이에요. 심지어 스크롤도 불가능해서 잘 안 보이는 채로 입력한 적 있지 않나요? 아니면 스크롤은 할 수 있지만 텍스트의 일부분이 가려지는 경우는요? 이게 그가 말한 열쇠구멍 문제입니다. 그런데 여기에서도 그런 문제가 있어요. "나는 항상 키보드로 작업을 해야 합니다. 5분이상 손을 떼고 있으면 안 되요." "너무 오래 손을 떼고 있으면 벌을 받게 되요." 무슨 이야기를 하고 있었지요? 아 DLL. 그래서 그 사람은 모든 DLL을 한 군데에 모았습니다. 그런데 DLL들이 서로 서로 의존하고 있다는 것을 잊고 있었어요. 원래 목표는 소스 코드 한 줄을 수정하면 관련된 DLL 하나 만 배포하는 것 있었지요. '#includes.' C++ 개발자들은 내가 무슨 이야기하는지 알 겁니다. 그런데 모든 '#includes' 들이 끔찍하게 얽혀있다는 것을 깨닫게 되었습니다. 모두 다시 컴파일하고 다 같이 배포해야만 했던 거지요. 결국 그들의 사업을 망했습니다. 오늘 강연의 주제는 컴포넌트에 대한 것 입니다. 컴포넌트 레벨의 디자인 문제들 이지요. 가장 먼저 해야 할 것은 '컴포넌트'에 대한 정의입니다. 컴포넌트는 무엇인가요? DLL입니다. 이제부터 말하는 컴포넌트는 DLL을 이야기하는 겁니다. 특정한 종류의 DLL 입니다. 동적으로 배포가 가능한 DLL이요. 동적으로 배포 가능한 DLL. 왜 동적으로 배포해야 할까요? 그건 소스 코드 한 줄이 변경되었을 때에 다른 DLL은 그대로 두고 변경된 DLL 한 개만 배포하려고 이겠지요. 'DLL 지옥'이 어떤 뜻이지요? Microsoft에서 자기들의 문제를 표현하기 위해 정의한 용어입니다. 그리고 닷넷이 나오면서 해결됐어야 했지요. 그 문구 기억하나요? "닷넷이 DLL 지옥을 치료합니다." 하하하! 치료하지 못했지요. DLL 지옥이란 수많은 컴포넌트들이 각각 다른 버전을 가지고 있을 때에 서로 맞는 짝이 어떤 것인지 모르는 상태를 이야기합니다. 그래서 Maven 같은 툴이 나왔지요. 닷넷에는 어떤 툴을 사용하나요? 여러 버전들의 DLL들을 관리해주는 툴 말입니다. 이 DLL의 버전 1과 저 DLL의 버전 3을 같이 다운받을 수 있게 하는 툴이요. 그런 툴이 있나요? 너겟 이요? (역주: NuGet과 음식 너겟(Nugget)의 발음이 유사함) 부드럽고 맛있는 치킨 너겟이요? 됐습니다. 화면의 저 그래프는 x의 제곱 그래프입니다. 그냥 x의 제곱 그래프에요. 하지만 다른 의미도 있습니다. 저 Y 축은 시스템의 모듈 개수 입니다. 모듈의 수에 따라서 가능한 의존 관계의 최대 개수입니다. 모듈 개수가 선형적으로 증가할 때에 의존 관계는 제곱에 비례해서 증가합니다. 이론적으로 연관 관계의 최대 개수는 모듈 수의 제곱에 비례합니다. 물론 시스템의 모듈들이 서로 마구 마구 의존하게 만들지는 않을 겁니다. 설마 그러고 있나요? 그래프를 봅시다. 모듈의 수에 따른 팀의 생산성을 보여주는 그래프입니다. 그나저나 이건 그냥 x의 마이너스 제곱 그래프입니다. 실제 데이터를 수집해서 만든 게 아닙니다. 그간 겪어온 다양한 사례들을 생각하면서 임의로 만들어 본겁니다. 시간이 지나면서 점점 개발 속도가 느려졌던 개발팀들을 생각하면서 말입니다. 혹시 경험해본 적 있나요? 초기에는 아주 빠른 속도로 개발을 하지요. 세상을 정복할 기세로 말입니다. 하지만 1년 지난 후에는 늪지대를 지나는 것처럼 느려져 있지요. 왜 느려졌는지도 몰라요. 예전에는 1주일이면 가능했던 일이 이제 석 달은 필요합니다. 그런데 막상 작업하면 버그가 너무 많이 생겨서 석 달도 부족해져요. 개발 기간이 길어지다보면 나타나는 문제들입니다. 그 원인 중 하나는 의존 관계가 많아지는 것 입니다. 왜 그럴까요? 저건 모듈 끼리 가질 수 있는 의존 관계의 최대치입니다. 이건 최소의 경우이지요. 연관된 모듈들은 서로 의존하기 마련입니다. 트리 형태일 때가 의존도가 제일 적습니다. 동적으로 링크한다면 더 적게 만들 수도 있겠지요. 하지만 보통은 작은 수의 의존 관계를 갖게 됩니다. 의존 관계가 몇 개 있지요? 1. 2. 3. 4. 5. 6. 모듈 7개 중에 6개로군요. 반면에 여기에는 아마도 49의 절반인가 보군요. 아니죠 그럼 너무 숫자가 적겠네요. 그럼 그냥 49개인가요? 모르겠네요. 어쨌든 아주 많습니다. 아마도 몇 인가의 제곱에 비례하겠지요. 어쩌면 0.5 * (n^2 + 1) 일 수도 있겠습니다. 뭐 그런 식이겠지요. 어찌되었건 의존 관계의 수가 아주 많습니다. 우리가 원하는 건 이게 아니라 저 트리 모양입니다. 저렇게 만들기 위해서 노력을 많이 해야 합니다. 그런데 어떤 얼간이가 이렇게 만들어버립니다. 비주얼 스튜디오는 이런 걸 금지합니다. 하지만 비주얼 스튜디오 솔루션 내에서만 해당되지요. 비주얼 스튜디오 솔루션에 속한 프로젝트들은 순환 참조를 할 수가 없습니다. 아주 바람직한 거지요. 순환 참조를 막아주니까요. 하지만 솔루션들 간에는 보장되지 않습니다. 비주얼 스튜디오의 솔루션이 여러 개 있다면요 서로 다른 솔루션에서 빌드된 것들을 링크한다면 여전히 순환 참조가 가능해집니다. 저렇게 순환 참조를 만들면 그냥 의존 관계가 하나 늘어난 것처럼 보이지요. 하지만 그냥 하나만 추가되는 게 아닙니다. 왜냐하면 6번이 2번을 참조합니다. 1번도 2번을 참조하지요. 의존관계는 전이됩니다. 그래서 6번은 4번과 5번을 의존합니다. 그리고 6번은 3번과 7번을 의존하지요. 결국 6번은 모든 모듈에 의존하는 셈입니다. 그래서 순환 참조를 가지는 순간 의존관계의 수는 급격하게 늘어나게 됩니다. 다시 n의 제곱 그래프입니다. 동시에 C++ 컴파에 걸리는 시간의 그래프이기도 하지요. 모듈을 추가함에 따라 정적으로 링크한다면 컴파일 시간과 링킹 시간이 제곱에 비례해서 증가합니다. 하지만 정적으로 링크하지 않는 경우에도 마찬가지입니다. 컴파일 시간은 모듈 수의 제곱에 비례해서 증가합니다. 모든 모듈들이 서로 의존하고 있다면 말입니다. '#include', import, using 구문들이 순환되게 참조하고 있다면 말입니다. 그렇게 되어 있다면 컴파일 타임이 아주 크게 증가해버립니다. C++은 특히 더 그렇습니다. 자바와 닷넷은 그렇지 않습니다. 자바와 닷넷의 컴파일 시간은 다른 기준을 가집니다. C++ 과는 다른 방식으로 소스 코드를 참조합니다. 자바와 닷넷은 선언문을 얻기 위해 바이너리 파일을 읽습니다. C++은 소스 파일을 읽지요. 그래서 C++에선 순환 참조가 발생하면 대가를 치룹니다. 매우 긴 컴파일 타임으로 말입니다. 아주 심하게요. 제곱에 비례해서 증가합니다. 그래서 모듈이 몇 개 늘어나면 컴파일 타임이 두 배로 증가해버립니다. 그래서 우린 뭔가 조치를 해야만 했습니다. 워드 커닝햄이 누구인지 아는 사람 있나요? 오. 몇 사람 있군요. 좋습니다. 모르는 사람들을 위해서 설명하면 워드 커닝햄은 위키를 개발한 사람입니다. 워드 커닝햄이 위키를 개발했지요. 그리고 캔트 벡을 도와 페어 프로그래밍과 테스트 주도 개발을 발명할 수 있게 했습니다. 그리고 대부분의 애자일 방법들도요. 워드 커닝햄이 누구인지 알아보세요. 아주 흥미로운 친구입니다. 아주 오래 전에 그는 스몰 토크 프로그래머였는데요 언젠가 물어봤습니다. "워드, 스몰 토크는 왜 사라진 거지?" 그가 말했습니다. "스몰 토크는 너무 쉽게 엉망으로 만들 수 있어서 사라졌어." "너 같은 C++ 프로그래머들은" 당시에 나는 C++ 프로그래머였습니다. "C++ 프로그래머들은 운이 좋은 거야." "네가 엉망으로 만들면 대가를 치루게 되거든." "스몰토크는 그러지 않았지." 자바나 C#도 그러지 않습니다. 대가를 치루지 않아도 됩니다. 어렵지 않게 엉망으로 만들 수 있습니다. 아주 복잡하게 엉키게 만들어버려도 깨닫지를 못해요. 다행스럼게도 비주얼 스튜디오는 일부나마 순환 참조를 막아주지요. 우리는 nLogN 수준의 생산성을 추구합니다. nLogN 이죠. n^2 대신에요. 컴포넌트간의 의존관계는 이렇게 생산성에 큰 영향을 미칩니다. 그리고, 1990년대와 2000년 사이에 큰 변화가 생겼습니다. 네트워크 속도가 비약적으로 향상되었지요. 요즘에는 1GB 크기의 파일을 받기는 어렵지 않습니다. 1GB를 10초만에 업로드 할 수도 있지요. 요즘엔 아주 쉬운 일이지만요 예전에는 훨씬 어려웠습니다. 그래서 예전에는 DLL을 하나만 배포하는게 유리하다고 생각했습니다. 요즘에는 그냥 모두 모아서 한 덩어리로 배포하지요. 왜요? 네트워크가 충분히 빨라져서 지요. 그냥 보내면 됩니다. 많은 DLL들을 그냥 하나로 링크된 바이너리처럼 다룰 수 있습니다. 그런데 또 다른 이슈가 있습니다. 여기 팀으로 일하는 사람이 얼마나 있지요? 그렇지요. 모두가 팀으로 일합니다. 아침 8시에 출근을 했다고 생각해봅시다. 디버깅할 프로그램이 있었어요. 제대로 동작하도록 하루 종일 일했습니다. 결국 퇴근 할 때엔 완벽하게 동작하게 만들었지요. 체크인하고 퇴근을 합니다. 그런데 다음 날에 출근해서 보니 동작을 하지 않았습니다. 왜 그럴까요? 누군가 당신보다 더 늦게 퇴근을 한 거지요. 그 사람이 당신이 사용하는 뭔가를 수정한 것 입니다. 그래서 다시 동작하도록 하루 종일 일했습니다. 그리고 다음 날 출근해 보니 또 동작을 안 합니다. 이런 문제가 얼마나 반복 될 수 있을까요? 아주 오랜 기간 가능할 겁니다. 규모가 큰 팀에서는 항상 발생할 수 있는 일입니다. 서로 서로 방해하게 되는 거지요. 물론 이런 문제를 해결할 툴들이 있지요. 소스 코드 관리 시스템이 있고요 그 밖에도 수 많은 좋은 툴들이 있습니다. 그럼에도 불구하고 제대로 관리하지 않으면 이런 문제는 계속 발생합니다. 그럼 프로젝트는 어떻게 관리해야 할까요? 여기에 원칙이 있습니다. Acyclic Dependencies Principle 이라고 부릅니다. (역주: 비 순환적인 의존관계의 원칙) 이런 뜻 입니다. "컴포넌트를 여러 개 가지고 있다면" "순환 참조가 없도록 배열해야 한다." 그래야만하는 이유는 여럿 입니다. 이미 하나는 이야기 했지요. 컴파일 시간이요. 또 의존 관계로 의한 부하도 이야기 했지요. 그리고 하나 더 있습니다. Alarm 모듈의 새로운 버전을 릴리즈하려고 합니다. Alarm 모듈을 담당하는 팀은 1.0 버전을 릴리즈하려고 작업 중입니다. Alarm 모듈은 다른 모듈을 사용하지 않습니다. 따라서 자유롭게 릴리즈할 수 있지요. 1.0 버전을 릴리즈하고 1.1 버전 개발을 착수합니다. 그런데 지금 Alarm 1.0 버전이 릴리즈되었니까 Elevator 모듈과 Conveyor 모듈도 릴리즈할 수 있게 되었습니다. 릴리즈 후에 두 모듈도 각각 1.1 버전 개발을 시작합니다. 이제 Transport 모듈도 릴리즈 합니다. 어떻게 돌아가는지 알겠지요? 버전 넘버가 밑에서부터 타고 올라갑니다. 1.0 버전이 밑에서 생성되면. 점 점 위로 전파되어 맨 위에까지 도달합니다. 버전 넘버가 밑에서부터 위로 전파되는 거지요. 자세히 보면 알게 될 겁니다. 버전 넘버가 전파되는 경로는 빌드 순서와 동일합니다. 의존 관계는 빌드 순서를 따라 전파됩니다. 그런데 어떤 얼간이가 이런 짓을 합니다. 누구지요? 나에요. 내가 그랬어요. 난 알람의 서브시스템을 담당하고 있는데 컨트롤 패널 화면에 메시지를 표시해야만 해요. 그런데 마침 컨트롤 패널에는 Display라는 펑션이 있더라고요. "오. 이걸 호출하면 되겠군." 하면서 사용했습니다. 문제 없이 호출할 수 있었어요. 컴파일 됐고, 동작도 문제 없었습니다. 그런데 다음 날 사람들이 화가나서 내게 찾아왔지요. "도대체 무슨 짓을 한 거야?" "그냥 컨트롤 패널의 Display 펑션을 호출 했을 뿐이에요." "화면에 메시지를 뿌려야만 했거든요." "그렇게 하면 안되지!" 왜 안되는 걸까요? 우선 저 모듈들은 어떤 순서로 빌드해야 할까요? 원래는 밑에서부터 위로 빌드해야 합니다. 그런데 지금은 밑이 없습니다. 따라서 올바른 빌드 순서가 없습니다. 컴포넌트 그래프에서 순환 관계가 있다면 올바른 빌드 순서를 정할 수 없게 됩니다. 따라서 시스템 동작도 정의할 수 없게 됩니다. "Undefined"가 무슨 뜻 이지요? "Undefined"의 정의가 무엇 인가요? 정해진 조건에서만 동작한다는 것 입니다. "Undefined" 인 것은 배포 전에는 잘 동작합니다. 하지만 배포되고 나면 동작하지 않게 되지요. 이런 순환참조가 있으면 시스템은 심각한 오류를 발생할 수 있습니다. 자바에선 흔히 볼 수 있습니다. 순환 참조가 있는 자바 시스템이 있다면...... 빌드는 할 수 있지요. 정확한 빌드 순서가 없더라도 말입니다. 그런데 테스트를 돌리면 실패합니다. 그런데 다른 순서로 빌드를 하면 테스트가 성공하기도 합니다. 테스트가 패스할 때까지 계속 빌드를 하는 회사도 본적이 있습니다. 그런데 더욱 심각한 문제가 있습니다. Conveyor 모듈을 릴리즈 하려고 합니다. 1.1 버전을 릴리즈 하려고 하지요. 그러러면 Alarm 1.1 버전과 같이 테스트해야 합니다. 하지만 Alarm 1.1 버전은 Control Panel 1.1 버전을 기다리고 있지요. 그리고 Control Panel 1.1 버전은 Transport 1.1 버전을 기다리고 있습니다. Transport 1.1 버전은 지금 릴리즈 하려는 Conveyor 1.1 버전을 기다리고 있습니다. 그러므로 모든 소스코드를 한군데에 모으기 전에는 릴리즈 할 방법이 없어집니다. 통합작업. 누구 기억하는 사람 있나요? 통합작업의 즐거움이요. 모든 시스템을 통합한 이후에 동작하게 하는 것 이지요. 그러는 동안에는 모듈끼리 서로서로 얽히게 되지요. 결국 아침에 출근했더니 코드가 동작하지 않는 문제가 다시 발생하게 됩니다. 그런데 더 큰 문제가 있습니다. Conveyor를 테스트하려면 Alarm이 필요한데요 Alarm은 Control Panel이 필요하고, Control Panel은 Revenue가 필요하고, Revenue는 데이터베이스가 필요합니다. 그리고 데이터베이스는 45분이나 걸려서 로딩했더니 테스트하자마자 죽어버립니다. 도저히 테스트를 실행할 수가 없어요. Conveyor 모듈 담당자들은 이야기 합니다. "대체 우리 모듈이 데이터베이스가 왜 필요한 거야!?" "음, 의존관계가 이상하게 꼬여서 데이터베이스가 필요한 거야." 프로그램을 실행하고 로딩된 DLL 목록을 보면서 이상하게 여긴 사람 없나요? "내가 왜 이런 것들을 사용하지?" C++ 개발자 중에 링크 목록을 보고 놀랐던 사람 없나요? "왜 이런 것들이 링크 목록에 포함되어있는거야?" "내가 왜 이런 것들을 사용하는 거지?" 순환 참조를 가지고 있기 때문입니다. 그래서 잡스러운 것들이 이것 저것 끌려오는 거지요. 그해서 첫 번째 원칙은 컴포넌트간에 순환 참조가 없어야 한다는 것 입니다. 그럼 어떻게 해결할 수 있을까요? 밑에 있는 모듈이 저 위에 있는 모듈의 펑션을 호출하려면요? 어떻게 하겠습니까? 음...... 컴포넌트를 하나 추가해도 되겠지요. 가능한 방법 중 하나입니다. Control Panel 컴포넌트에서 클래스를 하나 떼어내서 Display 컴포넌트에 추가합니다. Alarm 컴포넌트는 Display 컴포넌트를 호출할 수 있어요. Control Panel 컴포넌트도 Display 컴포넌트를 호출할 수 있습니다. 이렇게 하면 순환 관계를 깰 수 있지요. 일반적인 방법입니다. 이 모든 컴포넌트들은 DLL이었지요? 따라서 순환 관계를 추가되면 결국 DLL 수가 늘어나게 됩니다. 아마도요. 또 다른 해결 방법이 있습니다. Dependency Inversion을 사용하는 것이지요. 인터페이스를 하나 만듭니다. Display 인터페이스요. Alarm 서브시스템에 추가합니다. 그리고 Control Panel이 Display 인터페이스를 구현하게 합니다. 그러면 의존 관계가 뒤집히게 됩니다. 그리고 순환 관계도 선형으로 바뀝니다. 객체지향은 무엇인가요? 객체지향 프로그래밍이 무엇인가요? 우리가 왜 좋아할까요? 어쩌다 모든 프로그래밍 언어가 객체지향이 되었을까요? 벌써 30년동안 사용하고 있습니다. 어떤 것인지는 알아야겠지요. (청중) 현실 세계를 모델링 할 수 있습니다. 현실 세계를 모델링 할 수 있다. 감사합니다. 내가 이렇게 답하라고 저 사람을 여러분 사이에 심어놨습니다. 내가 저사람의 답변을 발기 발기 찢어버리려고요. 아닙니다. 아주 터무니없는 이야기에요. 객체지향이 실 세계를 모델 하기에 더 좋다는 생각은 말도 안됩니다. 그건 오래 전에 어떤 사람이 꾸며낸 이야기입니다. 관리자를 설득해서 1만2천 달러짜리 C++ 컴파일러를 사게 하려고요. 그 것 말고는 관리자를 설득할 방법을 못 찾았기 때문에요. 1만2천 달러. 초기 C++ 컴파일러는 매우 비쌌습니다. "1만2천 달러! 그 돈 주고 컴파일러를 사지는 않겠네." "하지만 그게 있으면 현실 세계를 더 잘 반영할 수 있습니다." "아! 그렇다면야......" 실세계를 더 잘 반영한다는 것은 완전히 어리석은 생각입니다. 객체지향이란게 그냥 데이터와 펑션들이 같이 모여있다는 것 이외에 뭐가 다를 게 있겠어요? 캡슐화요. 좋습니다. 캡슐화. 하지만 펑션들이 데이터 구조체를 가진다는 점은 객체지향이 아닌 언어와 다를 게 있나요? 쉽게 답하기는 어렵지요. 어떻게 다를까요? 네. 데이터 구조체를 펑션과 같이 넣어서 사용하고 있지요. 하지만 항상 그래왔습니다. 예전의 C 프로그래머도 항상 그래왔습니다. 데이터 구조체도 항상 같이 있었죠. 아주 유명한 책이 있었지요. "Algorithms plus data structures equals programs" 데이터 구조와 펑션이 같이 동작합니다. 객체지향에 특별할 것은 없습니다. 하지만 객체지향으로 인해 가능해진 게 하나 있긴 합니다. 예전에는 위험해서 잘 안 썼지요. 다형성입니다. 아주 사용하기 쉬운 다형성이지요. C에도 있긴 했습니다. 하드웨어에 독립적인 모든 OS는 다형성의 예입니다. 어떤 디바이스를 사용할지 모르고 프로그램을 작성할 수 있다면 분명히 다형적인 특징을 사용하고 있는 것 입니다. 하지만 대부분의 언어에서는 위험합니다. 적어도 예전에는 그랬습니다. 왜냐하면 펑션 포인터들을 만지작거려야 하거든요. 항상 위험한 작업이었지요. 객체지향의 도입으로 아주 편리하게 구현할 수 있게 되었습니다. 특별히 다형성을 생각하지 않아도 됩니다. 자바 같은 경우에는 모든 메소드가 다형적입니다. 선택의 여지가 없지요. C#은 선택할 수 있습니다. virtual 키워드를 사용하면 되지요. C++ 프로그래머도 선택할 수 있지요. virtual 키워드를 사용하면 됩니다. 소멸자에는 반드시 써야 하지요. 하지만 대부분 별로 신경을 쓰지 않아도 됩니다. 모든 펑션들이 다형적이에요. 특별히 신경 쓰지 않아도 됩니다. 왜 그럴까요? 펑션이 다형적 특징을 가지고 있다면 뭔가 근사한 일이 생깁니다. 컨트롤 흐름은 상위 클래스에서 하위 클래스로 향합니다. 하지만 소스코드의 의존관계는 반대로 하위 클래스에서 상위 클래스로 향합니다. 런타임 의존관계를 변경하지 않고도 소스코드의 의존관계를 뒤집을 수 있습니다. DLL을 어떻게 만들지요? 컴포넌트는 어떻게 만드나요? 다른 것들과 분리시켜서 만듭니다. 하지만 런타임 의존관계는 유지해야 합니다. 비주얼스튜디오 쓰는 사람들 ReSharper 사용하나요? ReSharper 사용하는 사람? 보세요. 모두가 사용합니다. 비주얼스튜디오가 ReSharper에 대해서 아나요? 아닙니다. 비주얼스튜디오가 ReSharper를 호출하나요? 그렇지요. 컨트롤 흐름은 비주얼 스튜디오에서 Resharper로 향합니다. 비주얼 스튜디오는 필요한대로 ReSharper의 펑션을 호출합니다. 하지만 비주얼스튜디오 소스 코드는 ReSharper의 소스 코드에 의존하지 않지요. 왜냐하면 의존 관계의 방향이 뒤집어졌기 때문이지요. 어플리케이션이 어떤 걸 호출할지 모르는 상태에서도 호출될 DLL들을 만들 수 있습니다. 의존 관계를 뒤집어서 말입니다. 의존 관계가 역전되는 거지요. 이 것도 순환 참조를 해결하는 좋은 방법입니다. 그럼 Control Panel은 Alarm 시스템에 플러그인이 됩니다. Alarm 시스템은 Control Panel에 대해서 모릅니다. Control Panel 은 플러그인이에요. Alarm 시스템은 어떤 플러그인이라도 사용할 수 있습니다. 어떤 것이든지 이 Display 펑션을 구현할 수 있습니다. 그러면 아주 많은 것들에 알람을 적용할 수 있는 거지요. 어떤 것에 의존하겠습니까? 안정적인 컴포넌트를 의존하겠습니까? 아니면 불안정한 컴포넌트요? 뻔한 질문입니다. 누구나 안정적인 것을 의존하고 싶어합니다. 그러면 안정적이라는 단어를 정의해봅시다. 내 레이저 포인터는 안정적인가요? 이건 변하지는 않지요. 하지만 안정적인가요? 안정성은 그렇다 아니다로 표현할 수가 없습니다. 안정성은 지속적인 특징을 가집니다. 그리고 변화 시키는 데에 필요한 일의 양으로 정의되지요. 힘이 많이 필요하면 안정적인 것 입니다. 조금의 힘으로도 변화시킬 수 있다면 불안정적인 것이지요. 저건 불안정적 입니다. 조금만 힘을 써도 상태를 변경시킬 수 있거든요. 이건 안정적이라고 말하진 않겠습니다. 뒤집는데 힘이 많이 들지 않겠거든요. 그리고 여기 강단은 그다지 안정적이지 않은 것 같아요. 조심해서 움직여야겠네요. 다시 질문해보겠습니다. 어떤 것을 의존하겠습니까? 쉽게 변경되는 것에 의존하겠어요? 아니면 잘 변경되지 않는 것에요? 소스코드를 수정하는 경우라면 요? 어떤 것에 의존 하겠습니까? 소스 코드 변경하기 어려운 모듈에 의존할까요? 아니면 소스 코드를 변겨하기 쉬운 모듈에요? 같은 답변입니다. 변경하기 어려운 것에 의존해야 합니다. 이유는 아주 간단합니다. 문자열 클래스를 사용하기 꺼려지나요? 아닙니다. 왜지요? 만약에 누군가가 문자열 클래스를 변경한다면 큰 대가를 지불할 겁니다. 문자열 클래스를 사용하는 여러분 보다 훨씬 더 괴로울 거에요. 그게 우리가 이야기하는 방정식입니다. 어떤 모듈을 사용하는 조건은 다음과 같습니다. 그 모듈이 변경됐을 경우 나보다 그 모듈의 개발자가 할 일이 더 많으면 사용합니다. 그게 적절한 조건이에요. 그런 경우엔 안심하고 사용할 수 있습니다. 그 모듈이 잘 변경되지 않을 것 같거나 변경되더라도 그걸 변경한 나쁜 자식이 합당한 대가를 치루게 되는 경우 말입니다. 그렇습니다. 우린 손쉽게 변경되는 것에 의존하지 않으려고 합니다. 잘 생각해보세요. 쉽게 변경되는 것에는 의존하지 않습니다. 시스템을 설계할 때에 쉽게 변경할 수 있게 만드는 곳이 있나요? 있습니다. 시스템 중 어떤 부분이 가장 쉽게 변경될 수 있어야 하지요? GUI입니다. GUI는 쉽게 변합니다. 특별한 이유 없이 바뀌기도 합니다. 사람들은 그냥 느낌 만으로도 GUI를 변경하기도 하지요. 제품 위원회에서 이야기합니다. "이봐, 우리 시스템은 좀 오래되 보여" 그게 무슨 말인가요? "우리 시스템은 좀 오래되 보여. 디자인을 바꿔야겠어." "마케팅 팀에서 결정 났어. 완전 새로운 Look & Feel을 입히자고." "동작을 변경할 필요는 없고" "GUI의 Look & Feel 만 변경하는 거야." GUI는 쉽게 바꿀 수 있어야 합니다. GUI 모듈의 소스 코드는 쉽게 변경할 수 있어야 합니다. 그 건 다른 모듈이 GUI 모듈에 의존하면 안 된다는 말이지요. 어떤 모듈의 소스 코드도 GUI 모듈을 참조해선 안됩니다. GUI 모듈을 향하는 의존 관계가 없어야 합니다. GUI 컴포넌트가 어플리케이션의 다른 모듈들을 의존해야 합니다. 다른 컴포넌트들은 GUI 컴포넌트에 의존하면 안됩니다. 그렇지 않으면 결국 시스템의 GUI는 수정하기 어렵게 될 겁니다. 시스템을 테스트하는 사람이 얼마나 되나요? 자동화된 테스트요. GUI를 통해서요. 두어 사람 있군요. GUI를 통해서 테스트를 한다고요. 그렇다면 쉽게 변해야 하는 것에 의존하고 있는 것 입니다. 그러면 결국 GUI 변경하기 어렵게 만들 겁니다. GUI를 통해서 시스템을 테스트를 한다면요. 15,000개의 테스트를 GUI를 통해서 수행하는 고객이 있었습니다. 앞에서 이야기한 그 고객입니다. 그 망한 고객이요. GUI 기반의 15,000개의 테스트요. 테스트가 너무 많아서 어떤 테스트인지도 모르고 있었지요. 그냥 모두 패스해야 한다는 것만 알았지요. 누군가 GUI를 변경한다면요 테스트 케이스 중 1000개는 실패하겠지요. 너무 많아서 수정하기도 힘들었겠지요. 그래서 아주 간단한 규칙을 생각해냈습니다. 무엇이었을까요? GUI는 변경하지 않는다! 그들은 GUI를 변경하기 어렵게 만들었어요. 테스트 케이스를 못쓰게 되는 경우도 있었겠지요. 12 M/M를 투자해서 GUI를 통한 테스트 수트를 만들었다고 합시다. 그런데 누군가 결정했어요. "GUI 디자인을 변경해야겠어." "기존 GUI를 걷어내고 새로운 GUI를 개발합시다." 테스트들은 쓸모가 없어집니다. 테스트 케이스를 다시 작성해야 합니다. 하지만 테스트할 시스템이 그 것만 있는 게 아니지요. GUI는 테스트하지 마세요. GUI를 통해서는 아무것도 하지 마세요. 어떤 의존 관계도 GUI를 향해선 안됩니다. 쉽게 변경하고 싶어하는 게 또 뭐가 있을까요? 데이터베이스요. 데이터베이스도 쉽게 변경하고 싶어하지요. 어플리케이션을 엎어버리지 않고 데이터베이스를 변경 할 수 있기를 원합니다. 어떤 의존 관계도 데이터베이스로 향하면 안되겠지요. 아무 것도 데이터베이스를 의존하지 않게 해야 합니다. 아무 것도 GUI를 의존하지 않게 해야 합니다. 불안정적인 것은 의존하면 안됩니다. 불안정성은 어떻게 측정 할 수 있을까요? 저기 보이나요? 저기 위에 있는 컴포넌트요. 저건 안정적인가요? 아니면 불안정적인가요? 많은 모듈들이 의존하고 있지요. 자기가 의존하는 것은 없습니다. 따라서 변경하기 어렵지요. 변경을 하게 되면 다른 모듈들에도 영향을 미칩니다. 따라서 저 컴포넌트는 다른 모듈들에 책임을 지게 되는 거지요. 음 이 컴포넌트는 독립적이군요. 다른 모듈을 의존하지는 않습니다. 책임감있고 독립적입니다. 마치 어른 처럼요. 안정적이고 어른과 같습니다. 저기 아래 컴포넌트를 보세요. 많은 모듈들을 이용하고 있네요. 저 컴포넌트를 이용하는 모듈은 없어요. 책임질 필요가 없겠지요? 그리고 의존적입니다. 청소년과 같습니다. 그리고 안정적이지 않습니다. 서로 완전히 반대되는 컴포넌트들이지요. 컴포넌트 종류의 양 극단이라고 할 수 있습니다. 아주 안정적인 어른과 아주 불안정적인 청소년이지요. 불안정적인 것은 쉽게 변합니다. 가능한 코드를 적게 넣어야겠지요. 안정적인 것은 변경하기 어렵습니다. 안정적인 정도를 측정할 수 있는 지표를 만들 수 있습니다. 'I'라고 부를 거에요. 'I' 는 다른 것을 이용하는 관계의 수를 나를 이용하는 관계의 수와 다른 것을 이용하는 관계의 수를 더한 것으로 나눈 값입니다. 곰곰히 생각해보면 'I'는 0부터 1 사이의 값이라는 것을 알게 될 것 입니다. 0은 안정적이고, 1은 불안정적입니다. 0은 어른이고, 1은 청소년입니다. 모두 의존 관계에 대한 이야기입니다. 자 그럼 원칙을 다르게 표현해보겠습니다. 컴포넌트는 자기보다 잘 변하지 않는 것에 의존해야 한다. 다르게 말한다면, 의존 관계의 방향은 'I'가 감소하는 방향으로 향해야 한다. 불안정성이 감소하는 것은 안정성이 높아지는 것 이지요. 참조하는 수와 참조되는 수를 가지고 간단하게 확인할 수 있어요. 순환 참조가 왜 나쁘지요? 안정적인 것이 잘 변하는 것을 이용하게되기 때문입니다. 그러면 새로운 문제가 생깁니다. 뭐냐하면 저 맨 밑에 있는 컴포넌트는 어떻지요? 안정적인가요? 아닌가요? 아주 안정적인 컴포넌트입니다. 맨 밑에 위치하기도 하고 많은 모듈들이 사용하고 있습니다. 변경하기 아주 어려워요. 저 컴포넌트를 수정하면 많은 모듈들이 영향을 받게 됩니다. 그냥 버전 넘버만 바뀌었는데도 말입니다. 저 밑에 있는 것들은 변경하기가 아주 까다롭지요. 하지만 벗어날 방법이 있습니다. 다형성을 사용하는 것 입니다. 어떻게 하면 쉽게 확장되게 만들 수 있을까요? 변경하기 까다로운 것이라도 말입니다. 추상 클래스로 만드는 것 입니다. 추상 클래스는 쉽게 확장할 수 있습니다. 직접 수정하지 않고도 시스템에 새로운 피처를 추가할 수 있습니다. 새로운 기능을 파생 클래스에 담아서 말입니다. 그래서 마지막 원칙은 다음과 같습니다. 밑에 위치할 수록 더욱 추상적이어야 한다. 저 위의 것은 구체적입니다. 불안정하고 구체적이지요. 저 밑의 것은 추상적이고 안정적입니다. 저 트리를 따라 내려갈 수록 추상성은 점점 커져갑니다. 추상성은 숫자로 표현될 수 있습니다. 추상 클래스의 개 수를 컴포넌트의 전체 클래스 수로 나누는 것 이지요. 그러면 0부터 1 사이의 'A' 값을 구할 수 있습니다. 0은 구체적인 것 이고요. 1은 모두 추상적인 것 입니다. 컴포넌트에 인터페이스 밖에 없는 거지요. 그러면 아주 흥미로운 일을 할 수 있습니다. 컴포넌트의 'A'와 'I'의 값을 합치면 1이됩니다. 'A' 가 1이어서 추상적이거나 'I'가 0이어서 안정적입니다. 'I'가 0인 경우 불안정적이고 'A'가 0인 경우 구체적입니다. 'A'와 'I'를 합하면 1이됩니다. 마법의 공식입니다. 'A' 더하기 'I'는 1입니다. 저기 위에 추상적인 어른이 있지요 다른 모두가 의존하고 있습니다. 안정적이란 말이지요. 저기 아래에 청소년이 있습니다. 다른 곳에서 사용하고 있지 않지요. 하지만 구체적입니다. 이 것은 무엇인가요? 'A'와 'I'의 합이 1인 것을 보여주고 있지요. 컴포넌트들은 저 두 끝 지점에 위치하는 게 가장 이상적이겠지요. 왜 그럴까요? 여기 왼쪽 위에는 어떤가요? 아주 추상적이지요. 아무 것도 의존하지 않습니다. 인터페이스가 구현하는 모듈이 하나도 없는 경우이지요. 쓸모 없습니다. 여기 쓸 데가 없는 것들입니다. 컴포넌트를 저쪽에 위치하게 하면 안됩니다. 그럼 여기 오른쪽 아래에는 어떤가요? 아주 구체적입니다. 모두가 의존하고 있습니다. 데이터베이스 스키마. 구체적입니다. 모두가 의존하고 있지요. 변경하면 아주 재미나겠지요. 그렇지요? 여기 밑에 위치하면 안됩니다. 아주 고통스러운 구역이에요. 컴포넌트는 저 두 지점에서 가능한 멀리 위치해야 합니다. 이상적으로는 컴포넌트를 여기 여기에 위치하면 좋겠지만 그렇게 하면 컴포넌트가 까다로워집니다. 그래서 최소한 이 선 위에 위치하도록 해야 합니다. 아니면 선에 가깝게요. 여기에서 마지막 측정 기준이 나옵니다. 'D' 입니다. 컴포넌트가 저 선에서 얼마나 떨어져있나를 의미합니다. 'D'는 'A' 더하기 'I' 빼기 1의 절대값 입니다. 계산하기 어렵지 않습니다. 'D'는 0과 1사이의 값입니다. 0은 선위에 있다는 뜻이고 1은 저 나쁜 두 지점에 있다는 것 입니다. 저 두 지점 중에 어느 쪽이냐는 절대값 기호를 빼면 알 수 있겠지요. 뭐 상관 없습니다. 'D'는 의존하는 관계와 의존되는 관계를 보고 알 수 있습니다. 추상적인 정도를 측정하는 것 이지요. 계산 자체는 어렵지 않습니다. 컴포넌트가 저 선위에 있는지 보세요 선 위에 있으면 추상적이고 의존되고 있지요. 선 위에 없으면 추상적이지만 의존되고 있지 않은 것 입니다. 아니면 아주 구체적인데 많이 참조되고 있는 것 입니다. 둘다 아주 나쁜 거지요. 많은 툴들이 이런 지표들을 자동으로 계산해줍니다. NDepend 써봤나요? NDepend도 이런 지표들을 계산해줍니다. 대부분의 정적 분석 툴들이 I, D 와 같은 지표들을 계산해줍니다. 컴포넌트들의 지표 값들을 쉽게 확인할 수 있습니다. 그리고 어떤 것들이 변경하기 쉬워야 할지 생각해보세요. 변경하기 쉬운 것들은 청소년과 같은 것들입니다. 변경하기 어려운 것은 성인과 같아야 하고 추상적이어야 합니다. 구체적이고 청소년 같은 것 들은 변경하기 쉬워야 합니다. 추상적이고 어른과 같은 것 들은 변경하기 어려워야 합니다. 질문있나요? PDP-8 어셈블리 코드로 시작해서 여기까지 왔군요. 질문 있나요? 없어요? 좋습니다. 이런 괜찮습니다. 좋아요. (질문 중) 네 (질문 중) 두 개의 버전이요. (질문 중) 하나의 컴포넌트가 2개의 버전으로 존재하는 경우요. 아주 어렵지요. 네 같은 컴포넌트의 각각 다른 버전의 파일들을 한 시스템에 두지 마세요. 그게 DLL 지옥입니다. 다른 사람 있나요? String 클래스의 위치는 어디냐고요? 아주 아주 좋은 질문입니다. String 클래스는 저기에 위치합니다. 최악의 자리지요. 하지만 아무도 변경하지 않습니다. 따라서 신경 쓸 필요 없습니다. 오늘의 모든 이야기는 현재 개발중인 것에 해당됩니다. 현재 변경 중인 것 이요. 우리 라이브러리의 변경되고 있는 것들을 아주 유의해서 봐야 합니다. 라이브러리 중에서 변경되지 않는 것이나 변경되지 않는 오래된 라이브러리의 것 들은 신경 쓸 필요가 없습니다. 그 중 많은 것들이 저런 곳에 위치하겠지만 괜찮습니다. 저기에 위치하는 것은 없을 것 입니다. 뭐 조금은 있을 수도 있겠지요 죽은 코드를 제거해본 적 있나요? 아무도 상속받지 않는 추상 클래스처럼요? 네 실제로 조금은 있을 수 있겠어요. String, Vector, 그 밖의 라이브러리 등이 저기에 위치할 수 있겠습니다. 하지만 신경 쓸 필요 없습니다. 쉽게 변경되지 않을 테니까요. 저 그래프로부터 제 3의 축이 여러분 쪽으로 향한다고 생각해보세요. 그게 가변성에 대한 축입니다. 이 화면의 그래프는 가변성이 1에 가까운 상황이죠. 가변성이 0인 경우는 신경 쓸 필요가 없습니다. 또 질문 있나요? 저기 뒤에요.