Modularity In Design

FrontPage|FindPage|TitleIndex|RecentChanges| UserPreferences P RSS
모든 이상적인 디자인은 변화에 잘 적응하는 것이다. 변화에 잘 적응한다는 것은 변화비용을 최소화한다는 것이다. 그러기 위해서는 변화 범위를 축소/국지화해야 한다.

그러므로

좋은 디자인은 모듈화가 되어야 한다.

ExtremeProgramming에서 TestFirstProgramming은 자동으로 이런 모듈화를 유도한다. 나중에는 OnceAndOnlyOnce에 입각한 ReFactoring이 역시 모듈화를 돕는다.

Evolutionary Design에서도 이 modularity가 중요하다.

한가지 예를 보자.

안간힘씨가 아주 복잡한 시계를 하나 만드는데 1000개의 부속을 결합해야 한다고 보자.

안간힘씨는 이 부속을 하나하나 조립해 나가는데, 만약 중간에 고객에게서 전화가 온다든지 해서, 조립하던 시계를 탁자에 내려놓기라도 하면 모조리 분해되어 새로 시작해야만 한다.

할만한씨는 안간힘씨가 조립하는 시계만큼 복잡한 시계를 조립한다. 총 1000개의 부속을 조립해야 완성된다. 그런데 할만한씨는 한번에 10개의 부품씩만을 하나의 컴포넌트로 안전하게 조립한다. 따라서 중간에 전화가 와도 다시 조립해야 할 부품은 10개를 넘지 않는다. 이렇게 부품 10개씩으로 구성된 컴포넌트를 다시 10개씩 조립하고, 이걸 또 다시 10개씩 조립해서 최종 시계를 완성한다. Modularity의 계층화인 셈이다.

부품을 하나 덧붙이는 때에 전화가 올 확률을 0.01이라고 봤을 때에, 평균적으로, 할만한씨가 안간힘씨에 비해 몇 배나 빠르게 시계 조립을 마칠 수 있을까?

1000회 조립하는 데에 걸리는 데 드는 확률적인 시간을 F(1000)라 하면, (그리고 한 번 조립하는데 드는 시간을 1이라 하면,)
(가정: 전화가 오면, 그 단위시간은 count하지 않는다.)
F(1000) = {F(999)+1}*0.99 + {2*F(999)+1}*0.01*0.99 + {3*F(999)+1}*0.01*0.99 +... 이 되고,
  (참고: {F(999)+1}*0.99 + {2*F(999)+1}*0.01 이라고만 계산하면 틀림)

근사하면,
F(1000) ~= 1.0101*F(999)+1
F(999) ~= 1.0101*F(998)+1


따라서 F(1000) = 1.0101^2*F(999) + 1.0101 + 1 이 되고, 계속 하면,
F(1000) = 1.0101^999*F(1) + 1.0101^998 + 1.0101^997 + ... + 1

F(1) = 1*(99/100) + 2*(1/100)*(99/100) + 3*(1/100)*(1/100)*(99/100) .... ~= 1.0101
F(1000) = 1.0101^1000 + {1.0101^999-1}/(1.0101-1) = 23140 + 2290803 = 2313943
따라서 안간힘씨는 2,313,943 단위시간이 걸리고,

F(10) = 1.0101^10 + {1.0101^9-1}/(1.0101-1) = 1.105 + 9.41 = 10.5
따라써 할만한씨는
10.5 * 100 + 10.5 * 10 + 10.5 ~= 1050 + 105 + 10.5 = 1165

따라서 시간차는 2,313,943/1165 ~= 1986 (배)
-- 지원

정답은 약 2000배 빠름.

이해하기 쉬운 간단한 개산(estimation)

일단 둘 다 하나의 "안정된" 컴포넌트를 만들 확률을 생각해보죠.

안간힘씨는 부품 하나 덧붙이는데 실패할 확률이 0.01이니까 성공할 확률은 0.99겠죠. 그런데 총 1000개의 부품을 조립하면서 한번도 실패하면 안되니까, 성공확률은 0.99^1000 이겠군요.

할만한씨는 0.99^10의 확률로 하나의 컴포넌트를 완성할 수 있겠군요.

뭔가를 성공할 확률이 p라면 그걸 평균 1/p번 시행하면 한번 성공할 수 있다는 말이 되죠. 예컨대, 주사위에서 숫자 6이 나올 확률은 1/6인데, 주사위를 던져서 6이 나올 때까지 평균 몇 번을 던져야 할까요? 네, 6번이죠.

따라서, 안간힘씨는 1/0.99^1000 번 시도만에 한번 성공하고, 할만한씨는 1/0.99^10 번 시도만에 한번 성공하겠군요.

그러면 안간힘씨는 총 1/0.99^1000-1번 실패하고 한번 성공하겠고, 할만한씨는 1/0.99^10-1번 실패하고 한번 성공하겠죠.

여기서 한번 실패할 때 드는 "비용"을 생각해 보죠. 즉, 한번 실패할 때 얼마 동안 시간을 낭비해야 하냐는 거죠. 예를 들어 300개 조립했다가 실패하면 300단위시간 낭비한 셈이죠.

부품 조립시 실패할 확률이 1/100 이므로 평균 100번 조립하면 하나 실패하겠군요. 즉, 안간힘씨 경우, 실패의 비용은 100단위시간이 됩니다.

그러면 안간힘씨는 1/0.99^1000-1 번 실패하고, 한번의 실패 당 100단위시간 소모하니까, 실패에 드는 시간은 둘을 곱하면 되겠네요. 시계 하나 완성하기까지 걸리는 시간은 이 값에 부품이 성공적으로 완성되는데 걸리는 시간 1000을 더하면 되겠네요.

따라서, 안간힘씨가 걸리는 시간은 (1/0.99^1000-1)*100+1000 입니다. 약 2317257 단위시간이 되네요.

할만한씨의 실패 비용은 얼마일까요? 할만한씨 경우는 한번의 컴포넌트 만들기 시도가 10번의 부품 조립으로 이루어져 있습니다. 시도 횟수가 아주 작죠. 게다가 확률은 0.01로 아주 낮습니다. 이 경우는 총 10번의 부품 조립시 어디에서 실패하느냐의 확률이 거의 동일할 수 밖에 없겠죠. 버스가 10분 배차 간격으로 있다면 우리가 기다리는 시간은 평균 5분인 것과 같은 원리로, 평균 실패 비용은 약 5가 됩니다.

따라서, 할만한씨가 하나의 컴포넌트 만드는데 걸리는 시간은 (1/0.99^10-1)*5+10이고 여기에 111을 곱하면(10개 묶음들을 총 111번 결합하므로) 전체 시간이 나오겠죠. 약 1169 단위시간이 됩니다.

2317257/1169 하면 대략 2000배가 나옵니다. 할만한씨가 2000배 빨리 일을 끝내겠죠.

만약 정확한 계산을 원한다면 각자의 실패비용을 조건확률을 이용해서, 해당 컴포넌트 조립에 실패한 경우에 i번째 부품 조립에서 실패할 확률들을 구해서 각각의 소모시간을 곱하고 모조리 더해주면 실패비용의 기대값(평균 걸리는 시간)이 나옵니다.

2316256.5/1173.57 = 1973.7 정도 됩니다. 정답하고 큰 차이가 없으니 우리의 개산은 성공적이었군요.

만약 수식이 의심되면 컴퓨터로 시뮬레이션을 해보면 됩니다. 안간힘씨 경우는 시뮬레이션 하는데 걸리는 시간이 길기 때문에 200번 정도만 했더니 평균적으로 2310242 단위시간이 걸리고, 할만한씨는 5000번을 했더니 1173.4가 나오는군요. 수식으로 계산한 1173.57과 거의 차이가 없지요?

지원씨에게는 다음에 볼 때 간소한 상품이 있겠습니다.


"; if (isset($options[timer])) print $menu.$banner."
".$options[timer]->Write()."
"; else print $menu.$banner."
".$timer; ?> # # ?>