'Framework'에 해당되는 글 84건

  1. 2009.07.06 한줄짜리 함수
  2. 2009.07.05 레이어의 속도
  3. 2009.06.25 read & write
  4. 2009.06.12 예언?
  5. 2009.06.11 최근에 책을 읽다가..
  6. 2009.06.08 패키지의 설계 원칙
  7. 2009.06.08 GUI TEST
  8. 2009.06.08 양화 구축 2
  9. 2009.06.08 AOP
  10. 2009.06.01 Self-Signed Java Applets
함수의 적정 최대 라인수가 얼마냐 하는 것은 아직까지 논의가 되고 있는 문제인데 보통의 경우 모니터의 화면을 넘기지 않을 정도라는 대체적인 합의는 있다. (대략 20라인 이내) 

Max는 그렇다 치고 Minimum은 어떨까? 
한줄짜리 함수는 가치가 있을까? 이전의 강의에서 나는 종종 그건 지나친것 같다라는 질문을 받곤 했다. 



한줄짜리 함수의 첫번째 유용성은 리팩토링 책에서 소개하듯이 가독성때문이다. 

1. if (date.before(SUMMER_START) || date.after(SUMMER_END))를

2. if (notSummer(date)) 와 같이 바꾼다면 (Decompose Conditional)

Reading하기가 좀더 쉽다. 아마도 notSummer는 private 함수일것이고 함수명으로 좀더 의미를 명확하게 전달하기 때문에 굳이 함수를 들여다봐야 해서 복잡해지진 않는다.(이런 가독성에 대해서는 이전글에서 언급했듯이 동전의 양면이다. 만약 함수명이 적절하게 지어지지 않는다면 좀더 복잡해질 것이다. Framework에서 구조의 문제도 마찬가지이다. )

그럼에도 한줄짜리 함수를 싫어하는 사람들은 꽤 있었지만 현재는 그 수가 많이 줄었다. 이는 툴의 발전에도 많이 영향을 받았다. 이전에 text editor에서 코드를 작성하던 쥬라기 시대에는 툴에 함수 바로가기 등의 assist 기능이 없어서 text search로 함수 내용을 확인해야 하던때라 가능한 하나의 함수에서 해주는게 미덕인 시절이 있었다. 

한줄 함수를 싫어하는 다른 이유로는 C 시절에는 Function Call Stack이 쌓이게 됨으로써 발생하게 되는 비효율을 이유로 들었으나 자바에서는 C와 달리 이러한 비효율이 성능상에서 차지하는 부분이 아주 작다. 이미 자바 라이브러리 자체가 수십개의 Function Stack Call을 하고 있으므로 거기에 몇개쯤 더한다고 거의 영향이 없다. (그러나 C나 C++은 영향이 있다. 그 영향 자체는 상대적이기에 많다 적다고 할수 없지만 확실히 자바보다는 영향이 많다.)




잘 알려지지 않는 한줄 함수의 두번째 유용성은 일종의 중개자로서의 역할인데
예컨데 System.out.println()을 예로 들어보자. 

최근에는 Unit시리즈의 유닛테스트를 많이 사용하고 있긴 하지만 그럼에도 유닛테스트하기 어려운 Swing이나 Persitence가 있고 또 가끔은 System.out.print으로 Debugging을 할때가 있다. 문제는 코드 중간중간에 써 놓은 System.out.println은 확인이 끝나고 처치가 곤란할때가 종종 있다. 

그렇다고 Logger를 쓰자니 잠깐 확인을 위해서 jar도 path에 걸어야 하고 설정도 해야하니 웬지 배꼽이 더 커 보인다. 

그럴때 Debug.debug() 클래스를 선언해서 쓰면 유용하다. 

예컨데 
public class Debug extends OutputStream{

private static void out(String message) {
System.out.println("   : " + message) ;
}

public static void error(Class cls, Object...objs) {
debug(cls, objs) ;
}

public static void debug(Object... objs) {
out(Arrays.deepToString(objs)) ;
}
public static void debug(Class cls, Object... objs) {
debug(Arrays.deepToString(objs) + " at " + cls.getName()) ;
}

public void write(int b) throws IOException {
System.out.print(b) ;
}
public static void happy(Object... objs) {
// TODO Auto-generated method stub
}
public static void happy(Class cls, Object... objs) {
// TODO Auto-generated method stub
}

}}
와 같이 별거 없는 클래스이다.(함수도 하나였는데 자꾸 쓰다보니 늘어났다.)

System.out.println() 대신 Debug.debug() 한줄함수로 바꾸면 생기는 이득은 멀까?

첫번째로 Reading이 쉬워지는 것외에 툴의 Call Hierarchy 기능을 통해 사용되고 있는 곳을 알수 있다. (System.out.println의 Call Hierachy를 찾으면 지나치게 많은 것을 찾는다.) 또는 잠깐의 조작을 통해 특정 메시지만 다른 outted message와 다르게 보이게 만들수도 있다. (System.out.println 함수를 바꿀수는 없지만 debug함수는 수정이 가능하다.)

둘째로 언제든 Logger 클래스를 사용하는 걸로 변신할수 있다. 이를테면 error() Method만 Logger 클래스를 사용하도록 이후에 수정에 가능하다. 시점이 나중이라는 것이 중요하다. 처음에는 그냥 쉽게 화면에 print 하는 방식으로 사용하다가 필요가 생기면 해당 함수를 수정하면 그만이다. 

한줄짜리 함수에 이런 장점이 생기는 근본 이유를 들여다 보면 좀더 흥미롭다. 왜 Debug.debug() 함수가 유용성을 가지는가? 그것은 우리가 System.out.print를 수정할 수 없기 때문이다. 만약 우리가 System.out.print를 컨트롤 할 수 있었다면 debug함수를 작성해서 수정할 필요없이 out class의 println 함수를 직접 수정하면 그만이다. 

앞서(4달전에:) 변화의 속도에 대해서 말한적이 있는데 현재 작성하고 있는 코드와 자바의 java.lang 패키지는 변화의 속도가 다르다. 그리고 그 변화의 속도가 다를때 부동산 중개인의 존재이유와 마찬가지로 우리는 중개자 클래스 혹은 함수가 필요하다. 다시 말해서 단 한줄짜리 함수라고 하더라도 해당 함수의 변화의 속도가 다르다면 그 함수는 유용성을 가질때가 많다. 

사실 대부분의 프레임워크와 마찬가지로 마치 빅뱅이론처럼 Logger Framework는 이러한 한줄짜리 함수에서 출발했다. 누군가 System.out.println으로 화면에 메시지를 보여주는게 관리상 효율이 좋지 못하다는 걸 깨달았고 한줄짜리 함수를 만들었는데 거기에 기능을 덧붙이고 유연성을 더하고 예외도 고려하고 등등을 하다보니 이게 Logger Framework가 되었다. 

그러나 그렇다고 해서 우리는 처음부터 System.out.println과, 보다 설정하기도 귀찮고 사용하기 어려운 Logger Framework 두가지중에서 꼭 하나를 결정해야 할 필요는 없다. 그런 선택은 미루다가 나중에 고를수 있거나 혹은 제 3의 길의 선택할 수 있는 방향을 열어두는게 좋다.

즉 한줄짜리 함수는 변화의 속도가 다른 Layer사이에서 그 속도의 조정과 완충을 담당하는 중재자 역할을 할때 새로운 효용성을 발견할 수 있다. 자신이 컨트롤 할수 없는 함수(System.out, Logger 등등)을 5번 이상 호출해야 한다면 이런 한줄짜리 중개자 함수를 고려해 보자. 



'Framework > 아키텍쳐 일반' 카테고리의 다른 글

여섯번째 계  (0) 2009.07.14
호출의 빈도  (0) 2009.07.08
예언?  (0) 2009.06.12
패키지의 설계 원칙  (0) 2009.06.08
양화 구축  (2) 2009.06.08
Posted by bleujin
Framework/Another Lore2009. 7. 5. 20:31
앞서 소개한적이 있지만 

"전산학의 모든 문제는 또 다른 수준의 간접층으로 해결할 수 있다" 
라고 말한 휠러는 

"그러나 그러면 또 다른 문제가 생기는 것이 일반적이다" 라고 덧붙인 적이 있다. 

"또다른 문제"의 대표적인 예로는 공간과 시간적 부담과 코드의 가독성 등을 들 수 있다. 

이 포스팅에서는 이중에서 시간적 부담 즉 속도에 관해서이다. 


일반적으로 대부분의 프레임워크는 하나의 간접층(Layer)이며 위의 명제를 피할 수는 없다. 그러나 프레임워크를 쓴다고 항상 느려진다고 할 수 없는 이유가 있다. 

만약 A -> B 과정이 1초가 걸렸다면
A -> A' -> B 과정은 1초보다 작게 걸린다는 것은 논리적으로 불가능하다. 

그러나 시간은 연속성의 특징을 가지고 있다. 무슨말이냐면 프로그램에서의 시간 혹은 속도의 개념은 하나의 event개념이 아니다. 프로그램을 작성하면 무수히 많은 A -> B 과정을 작성하게 된다. 

이를테면 A가 Client Call이라고 하고 B를 Engine라고 가정했을때 (Engine에는 DB나 NS등의 여러가지를 대입할 수 있다.)

1. Client Call(Engine Library) -> Engine과
2. Client Call(Framework Call) -> Framework(Inner Use Engine Library) -> Engine 라는 경우를 상상해 보자. 

하나의 원자 동작을 보았을때 1은 2보다 항상 속도가 빠르다. 그러나 실제 프로그래밍에서는 2가 종종 더 빠른 이유는(잘 만들었을 경우를 가정했을때...) 연속성의 이유때문이다. 함수 하나 혹은 command 하나는 말 그대로의 원자 event로 이루어지지 않는다. 

그리고 이것때문에 이를테면 1의 동작에 0.1초가 걸린다고 하고 10번 반복한다고 했을때 1은 1초가 걸리지만 2는 1초 이하로 떨어뜨릴수 있다. 

좀더 IT적인 용어로 말하자면 Context Switching 비용 때문이다. 

예를 들어
Row row = Engine.executeCommand() ;
if(row.exist()) Engine.updateCommand() ;
else Engine.insertCommand();

와 같은 코드의 경우 Engine의 row의 exist 여부를 체크하여 2번의 Engine Call을 실행한다. 이 과정에서의 속도는 (0.1 + a(context switching cost)) * 2 + b(Caller cost) 가 소모하게 된다. 

만약 위의 과정을 
Framework.mergeCommand() 라고 바꿀수 있다면 0.1 * 2 + a(context switching cost) + b'(Maybe Lower Caller Cost) + c (framework inner handling cost) 로 수식이 바뀐다. 만약 여기서 a + b> b' + c라면 프레임워크를 사용함으로서 속도가 향상되었다고 말할 수 있다. 추가적으로 좀더 간결하게 프로그램을 작성할 수 있다. 여기서 간결함이란 문제는 이전글을 참조하자. 

3번의 동작이 연속으로 이루어진다면 2a + b> b' + c 정도만 해도 향상될 수 있다. 


두번째 속도가 빨라질 수 있는 이유로 
앞서 원자 event라고 가정했던 1의 과정조차 실제로는 바깥에서는 보이지 않는 내부적으로는 연계된 다른 과정을 거치게 된다.

앞서 원자 action event라고 했던 1이 사실은 
Client Call -> Inner Action 1 -> Inner Action 2 -> Engine Inner으로 이루어져 있다가 가정하자. 
각각의 비용은 a + b + c + d  라고 가정하자. 

만약 
Client Call -> Inner Action 1 -> NewFramework -> Inner Action 2 -> Engine Inner로 작성하였다면..
a + b + @(framework cost) + c + d 의 비용이기 때문에 역시 단순 논리로 봤을때는 빨라지지 않는다. 

그러나 프로그래밍이 다중 사용자 환경(One User 프로그램의 경우에도 Multi Thread로 작성된다면 마찬가지이다. )이고 Action 2가 공유될수 있는 Action이라고 한다면 얘기가 달라진다

Inner Action2을 사용자 혹은 여러 Thread가 공유할 수 있다면

N번의 Call이 발생했을때. 
N(a + b + c + d)의 비용이 N(a + b + @ + d) + c의 비용 수식으로 바꿀수 있다. 
이는 c의 cosr가 total cost에서 얼마의 비중인가에 따라 그 가치가 달라진다. 잘 알려진 예제로 Connection Pooling 같은 과정을 생각한다면 이해하기 쉽다. 

# 컨텍스트 스위치 context switch
멀티테스킹 OS는 서로 다른 처리를 수행하는 처리 단위로 프로세스/쓰레드를 짧은 시간에 전환함으로써 병렬처리를 실현하고 있다. 이때의 프로세스/쓰레드 전환처리를 컨텍스트 스위치라고 한다. 이렇게 컨텍스트 스위치할때 멀티쓰레드는 메모리 공간을 공유하기 때문에 메모리 공간의 전환 처리는 생략할 수 있다. 

메모리 공간을 전환하지 않게 되면 CPU 상의 메모리 캐시(정확히는 TLB Translation Lookaside Buffer : 메모리의 가상 주소를 물리 주소로 변환하는 처리를 고속화하기 위한 캐시로 CPU 내부의 구조다. 컨텍스트 스위치가 일어나면 TLB가 초기화되는데 이 영향에 의한 TBL 캐시 미스는 상대적으로 비용이 많이 든다.)를 그대로 유지할 수 있는 등의 큰 이점이 있으므로 멀티 프로세스에 비해 성능에 미치는 영향은 현저하다. 

이러한 이유로 위의 경우 자바와 예로 든 엔진(예컨데 DB)의 변환은 멀티 프로세스 관계일때 더 차이가 두드러진다. 





원래 주제와 좀 떨어지지만 휠러 얘기가 나왔으니 "발생하는 또다른 문제"중 가독성의 얘기를 해보자. 

여기서의 가독성은 동전의 양면과 같다. 사실 맨 처음 웹 프로그래밍을 한다고 했을때 스프링을 사용하는 것은 그다지 좋지 못하다.(라고 생각한다.) 스프링의 스펙은 그리 만만치 않다. 오히려 기본적인 서블릿 프로그래밍보다 더 많은 걸 알아야 한다. 스프링 프레임아웤의 유용성을 떠나 아이러니한것은 중급개발자의 반복작업을 줄여주기 위한 프레임워크가 오히려 초보개발자용 프레임워크로 둔갑한다는 것이다. 

어쨌거나 휠러가 지적한 것은 프레임워크의 겉핧기 사용등의 프레임워크의 내부에 대한 이해 없이 접근하면 작성하는 프로그래머도 또 그걸 읽는 프로그래머(그 사람이 해당 프레임워크에 대해 알고 있는지와 상관없이)도 고통이 된다는 사실을 말한 것이다. 

앞서 동전의 양면이라고 한것은 프레임워크에 대한 많은 학습없이 직관적으로 사용이 가능하고 기대대로 프레임워크가 작동한다면 오히려 가독성에 도움이 될 수 있다는 의미이다. 

그런면에서 프레임워크의 직관적 사용은 좋은 프레임워크를 결정하는데 핵심이 된다. 스프링 프레임워크가 직관적이지 못한것은 어찌보면 프레임워크가 커버하는 범위와 관계가 있을지 모른다. 스프링 프레임워크는 너무 많은 일을 하는 프레임워크이고 또 유연성을 위해 XML 형식의 configuration이 지나치게 많고 복잡해서 휠러의 또다른 문제를 벗어나지 못했다. 











'Framework > Another Lore' 카테고리의 다른 글

언젠가...  (0) 2009.08.14
XUL 단상  (0) 2009.07.15
read & write  (0) 2009.06.25
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
Posted by bleujin
Framework/Another Lore2009. 6. 25. 10:47

습관은 관습을 낳고 관습은 편견을 낳는다. 

우리는 bean 객체를 많이 봐왔기 때문에 getMethod가 있으면 setMethod도 있어야 할 것이라고 생각한다. 물론 이것이 틀렸다고는 할수 없지만 가끔은 생각을 바꿔보는게 도움이 될 수 있다. 

예컨데 다음 상황을 보자. 

Node rootNode = session.getRootNode() ;
NodeType empType = getNodeType("employee") ;

Node newNode = rootNode.createChild(empType, "abcd") ;


이 상태에서 newNode를 repository에 저장하는게 좋을까? 아니다. 그러기엔 너무 일러보인다. 왜냐하면 아직 실제 값을 설정하지 않았기 때문이다. 

그 보다는 
newNode.setProperty("ename", "bleujin") ;
newNode.setProperty("empNo", 20) ;
와 같이 값을 설정하고 

newNode.save() 를 호출했을때 repository에 저장하는게 보다 효율적일 것이다. Transaction Handling이 가능하고 setProperty를 호출할때마다 DBCall을 하는것보단 훨씬 효율적이다. 


다만 위의 경우 문제가 있다. 

다음 상황을 상상해 보자. 

Node parent = rootNode.createChild(empType, "parent") ;
Node child = parent.createChild(empType, "bleujin") ;
child.setProperty("ename", "bleujin") ;
child.setProperty("empNo", 20) ;
child.save() ;

별문제가 없어보인다. 
그러나 위와 같이 현재 Session의 모든 PendingEvent를 저장하는 session.save() 를 호출하지 않고 child.save() ;를 호출했을 경우에 (물론 API 상으로는 아무런 문제가 없다.) parent가 저장되지 않은 상태에서 child가 먼저 저장될 수 있는가? 에 대한 의문이 든다. 더군다다 child만 save()하고 parent는 일부러 혹은 실수로 save를 하지 않거나 혹은 parent.save()를 호출했을 경우에 에러가 발생했을 경우를 상정해 보면 문제가 더욱 복잡해진다. 

API상으로는 문제가 없지만 child만 save가 되고 parent는 save 되지 않았을 경우는 정상적인 상태가 아니다. 그렇다고 맨 처음의 방식으로 돌아가는 것도 Transaction이 보장되는 것은 아니다. 각각의 호출마다 repository Call을 했을 경우 첫번째와 두번째 사이에 에러가 발생할 수 있다는 것은 마찬가지 이다. 

물론 사용자 실수야 라고 탓이라고 문제를 회피할 수도 있겠지만 이는 올바른 방법이 아니다. 순서에 의존하는 API는 (예컨데 a를 호출하고 난 다음에 b를 호출하고 그 다음에 c를 호출하고..) 사용하기 복잡하고 실수를 가져오는게 당연하다. 

이 문제를 해결하기 위해 AL은 존재와 무존재를 구분한다. 
예컨데 


interface INode {
    .......
    getterMethod() ;
    setterMethod() ;
    Node retrieve(...) ;
    Node save() ;
}

interface NodeTemplate extends INode {
     ....
}

interface Node extends INode {
    NodeTempalte createChild(NodeType nodeType, String name) ;
}

와 같이 인터페이스를 분리하면 문제는 명확해진다. 
ISP(Interface Segregation Principle)의 의미는 어렵지 않지만 책임의 한정이 어렵다. 여기서는 책임을 read책임과 write책임으로 분리한 것이다. 


NodeTemplate parent = rootNode.createChild(empType, "parent") ;
위 상태에서 NodeTemplate는 createChild Method를 호출할 수 없기때문에 

Node savedParent = parent.save() ; 를 호출한 이후 child를 만들거나 아니면 session.save()를 호출하여 pendingEvent를 같이 처리해야 한다. 


위 예제에서 좀더 나아가서 getMothod용 Node와 setMethod용 Node를 구분한다면 개선의 소지가 더 있다. 이를테면 Setter가 없는 Node를 통해 더 가벼운 Node를 만들 수 있게 된다. 그리고 Getter가 필요없는 Node를 통해 더 빨리 Node를 얻어올 수 있다. 




'Framework > Another Lore' 카테고리의 다른 글

XUL 단상  (0) 2009.07.15
레이어의 속도  (0) 2009.07.05
최근에 책을 읽다가..  (0) 2009.06.11
AL : Permission  (1) 2009.04.30
AL : 현재의 난제들  (0) 2009.04.30
Posted by bleujin

이를테면 나는 내일 이시간에 내가 무엇을 하고 있을지 약 80%의 확률로 맞출수 있다고 하자. 아마도 컴퓨터 앞에서 자판을 투닥거리고 있을 것이다.

그럼 내가 1년후에 무엇을 하고 있을지 맞출수 있는 확률은 얼마정도일까? 간단한 확률의 문제이다. 다음날 무슨 일을 할지가 독립변수라면 1년후에 내가 무엇을 하고 있을지 맞출 확률도 80%확률이어야 한다. 하지만 다음날 무슨 일을 할지는 독립변수가 아니고 내일모래 무슨일을 할지는 내일 무슨일을 할지에 영향을 받는다. 즉 내일 예상과 다른 일이 일어날 확률이 20%이고 그중의 반은 다음 날들의 일에 영향을 미친다고 하자. 그럴경우 내가 1년후에 무슨일을 하고 있을지 맞출수 있는 확률은 고작 2%에 불과하다.(0.9^365) 그리고 이것도 내일 무슨 일을 하고 있을지 맞출수 있는 확률이 80%라는 아주 낙관적인 기대에서 출발한 것을 잊지 말자.

이런걸 흔히 불확실성의 연속이라고 하는데 그래서 프로젝트에서도 가능하면 3월 이상의 일은 예측이라기 보다 예언이라고 해두는게 좋다. 컨설팅의 비밀이라는 책에서 나오는 의미없는 차이 + 의미없는 차이 + 의미없는 차이.... 는 의미 있는 차이라고 하는 예와 비슷하다. 

'Framework > 아키텍쳐 일반' 카테고리의 다른 글

호출의 빈도  (0) 2009.07.08
한줄짜리 함수  (0) 2009.07.06
패키지의 설계 원칙  (0) 2009.06.08
양화 구축  (2) 2009.06.08
AOP  (0) 2009.06.08
Posted by bleujin
Framework/Another Lore2009. 6. 11. 20:19

AL 개발하면서... 코드가 막힐때면 책을 읽곤 하는데..
"드리밍 인 코드"에 소개된 오픈소스 "챈들러" 프로젝트의 백엔드 저장소와 AL이 상당히 비슷하다는 걸 알았다.

AL은 Node와 Node의 Link로 정보를 저장하는데.
챈들러는 <this> <has-relationship-with> <that> 형태로 저장하는 RDF(Resource Description Framework)를 사용하고 있었다. 챈들러라는 프로젝트는 2003년쯤에 우연히 관련자료를 찾다가 알게된 프로젝트인데 당시에는 개인정보관리 시스템 정도로  알려져 있었지만 오픈된 내용이 거의 없어서 곧 잊혀졌다. (고작해야 1시간 정도 살펴본것뿐이긴 하지만 이걸 기억하는건 당시 프렌즈라는 미국 드라마를 아주 좋아했기 때문이다. 물론 챈들러 프로젝트의 챈들러는 프렌즈의 챈들러가 아니다. )


RDF(이런 용어가 존재한다는 것도 처음 알았다)는 주로 학구적인 성격이 강하기 때문에 구현체가 많지 않지만.
Phthon 구현체인 ZODB는 아직 자세히 살펴보진 않았지만 AL의 현재의 모습과 아주 비슷하다 -ㅅ-..

혼자 하는 AL이니 머 수십명 이상이 하는 ZODB나 챈들러와는
세부적인 모습은 다르지만.. 목적지가 같다..이런...

본래 오픈소스에서 비슷한 걸 발견하면 기뻐해야 겠지만..
첫번째.. 거의 실패 혹은 잊혀진 프로젝트라는 것이고..
두번째.. 파이썬으로 되어 있어서 그들의 경험으로부터 배우기가 쉽지 않기 때문이다. 췟.




'Framework > Another Lore' 카테고리의 다른 글

레이어의 속도  (0) 2009.07.05
read & write  (0) 2009.06.25
AL : Permission  (1) 2009.04.30
AL : 현재의 난제들  (0) 2009.04.30
AL : Workspace  (0) 2009.04.28
Posted by bleujin

패키지의 설계 원칙은 클래스의 설계원칙과 기본적인면에서 큰 차이가 없다. 현재까지 잘 알려진 설계 원리들은 다음과 같다.


CRP (Common Reuse Principle) - 패키지의 클래스들은 전체적으로 재사용됨.

CCP (Common Closure Principle) - 패키지의 클래스들은 동일한 유형의 변경에 대해서 닫혀있어야 함. 만일 클래스가 변경되어야 한다면 패키지의 모든 클래스들은 마찬가지로 변경되어야 함.

SOC (Separation Of Concerns) - 여러 관심사를 혼합시키지 마라.

세개 모두 High Cohension에 관한 얘기이다. 하나의 패키지는 하나의 클래스와 마찬가지로 Single Responsibility를 가져야 한다.



ADP (Acyclic Dependencies Principle) - 패키지들 간의 의존성 구조는 비순환구조이어야 함. A패키지의 일부 클래스가 B패키지를 참조하고 B패키지의 일부 클래스가 C패키지를 참조하고 C패키지의 일부 클래스가 A 패키지를 참조하는 경우를 순환 패키지 구조라고 하는데

SDP (Stable Dependencies Principle) - 패키지는 최소한 그 자체로 안정적인 패키지들에만 의존해야 함. 를 통해 예방할 수 있다. concrete 클래스가 추상클래스 혹은 인터페이스에 의존하는 것처럼 패키지도 좀더 안정적이고 추상적인 패키지에 의존해야 한다. 예컨데 java의 패키지나 apache의 stable project, 일반적인 프레임워크는 상대적으로 더 안정적이다.


SAP (Stable Abstractions Principle) - 패키지가 점점 더 안정될수록 점점 더 추상화되어야 함. 즉 SDP에 의거해 더 추상화된 패키지에 의존해야 한다는 말로 설명할 수있다.



패키지를 만들때 흔히 하는 잘못들은 A Interface의 구현클래스를 모두 하나의 패키지에 넣어야 한다고 생각하는 것이다. 물론 그럴 경우도 있지만 꼭 그래야 할 필요는 없다.

두번째로 클래스의 이름이 모든 패키지에 대해 전역으로 유니크해야 한다고 생각해서 매우 긴 클래스 이름을 만드는 경우이다. 역시 이는 늑대를 피하다가 호랑이를 만나는 실수가 될 수 있다.  

'Framework > 아키텍쳐 일반' 카테고리의 다른 글

한줄짜리 함수  (0) 2009.07.06
예언?  (0) 2009.06.12
양화 구축  (2) 2009.06.08
AOP  (0) 2009.06.08
Self-Signed Java Applets  (0) 2009.06.01
Posted by bleujin
Framework/예외처리2009. 6. 8. 20:04

흔히 알려진 테스트 하기 어려운 모듈은 네트워크, 데이타 베이스, GUI 인데 특히 Swing으로 제작된 GUI 프로그래밍을 테스트 하는 것은 꽤 어렵다. 

사실 가장 좋은 방법은 그냥 View 모듈을 Model과 Controller에서 최대한 분리하는 방법이 최선이긴 하다. 그걸로 충분하지 않다면 아래와 같인 자동 로봇을 시도해 볼 수 있다.

아래 코드는 특정 시나리오대로 프로그램을 실행하면서 마지막에 해당 GUI 화면을 갭쳐한다.

package com.bleujin.thinlet.sample.robot;

import java.awt.AWTException;
import java.awt.FlowLayout;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

public class RobotTest {

  public static void main(String[] args) {

    ButtonFrame frame = new ButtonFrame() ;
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE;
    frame.setVisible(true;
    
    GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice screen = environment.getDefaultScreenDevice();

    try {
      Robot robot = new Robot(screen);
      run(robot);
    catch (AWTException ex) {
      ex.printStackTrace();
    }
  }

  private static void run(Robot robot) {
    robot.keyPress(' ');
    robot.keyRelease(' ');

    robot.delay(2000);
    robot.keyPress(KeyEvent.VK_TAB);
    robot.keyRelease(KeyEvent.VK_TAB);
    robot.keyPress(' ');
    robot.keyRelease(' ');

    robot.delay(2000);
    robot.mouseMove(20050);
    robot.mousePress(InputEvent.BUTTON1_MASK);
    robot.mouseRelease(InputEvent.BUTTON1_MASK);

    robot.delay(2000);
    BufferedImage image = robot.createScreenCapture(new Rectangle(00450350));

    ImageFrame frame = new ImageFrame(image);
    frame.setVisible(true);

  }
}

class ButtonFrame extends JFrame {
  private JButton plainJButton; 

  public ButtonFrame() {
    super("Testing Buttons");
    setSize(450350;
    setLayout(new FlowLayout())// set frame layout

    plainJButton = new JButton("Plain Button")
    add(plainJButton)

    ButtonHandler handler = new ButtonHandler();
    plainJButton.addActionListener(handler);
  

  private class ButtonHandler implements ActionListener {
    public void actionPerformed(ActionEvent event) {
      JOptionPane.showMessageDialog(ButtonFrame.this, String.format("You pressed: %s", event.getActionCommand()));
    
  
  
  
}

class ImageFrame extends JFrame {
  public ImageFrame(Image image) {
    setTitle("Capture");
    setSize(450350);
    JLabel label = new JLabel(new ImageIcon(image));
    add(label);
  }

}

'Framework > 예외처리' 카테고리의 다른 글

예외 수집기  (0) 2009.03.26
exception framework  (0) 2009.03.10
checked vs runtime  (0) 2009.03.07
checked exception의 문제  (0) 2009.02.09
예외 처리 격언  (2) 2009.02.09
Posted by bleujin


IT는 다른 학문에 비해 비교적 신입이기 때문에 다른 학문에서 나왔지만 IT에 적용되는 이런저런 법칙들이 있다. 잘 알려진 8:2이 법칙은 성능이나 오류의 문제를 설명하는데 사용되곤 한다.

그밖에도 양화구축 - 혹은 16세기 영국 재무장관 그레셤의 이름을 딴 그레셤의 법칙이라는 게 있다. 

16세기 영국의 주요 화폐는 금화와 은화였다. 처음에는 금화의 포함된 금과 은의 가치와 금화의 가치가 거의 차이가 없었다. 그러나 화폐의 유통이 활발해지면서 화폐를 만들 금과 은이 부족해지면서 점차 금과 은의 함량을 줄인 화폐를 발행하였다.

일반 사람들로서는 최악의 상황에서 언제든 동일한 가치로 교환할수 있는 이전의 금화 은화는 자신의 금고에 두고 이 후에 발행된 함량이 부족한 화폐(악화,惡貨, bad money)만을 사용하게 되는데 이는 화폐의 신뢰문제로 이어지게 되었다.

근세 화폐 경제 체제에서 악화(함량이 부족한 금화)가 양화(1:1비율의 금화)를 구축하는 현상이 심화되자, 사람들은 통용되는 금화와 은화를 믿지 않게 된다. 일일이 금과 은의 함량을 재면서 거래를 하다 보니 화폐의 의미가 크게 퇴색하고 말았다. 결국 근세 화폐 경제 체제가 붕괴 직전까지 가고 말았다.

대항해 시대의 영국은 당시 국제통화로서의 가치를 잃고 싶지 않았고 당시 영국의 재무관이었던 T. 그레셤은 엘리자베스 여왕에게 이런 상황을 개탄하는 편지를 올렸다. 악화를 개주(改鑄)하는 작업을 거치지 않고는 영국이 외환을 지배할 수 없다는 취지였다. 19세기 중반 H. 마크로드는 그레셤의 보고에 ‘그레셤의 법칙’이라는 이름을 붙였고, 이는 훗날 경제학사에 가장 유명한 법칙의 하나가 됐다. 오늘날에서는 나쁜 것이 좋은 것을 압도하는 현상 일반을 지칭하는 용어가 됐다.

최근의 신용중심으로 변한 금융시장에서 그레셤의 법칙은 그 의미를 찾기 힘들지만 IT에서는 종종 사용되곤 한다. 대표적으로 좋은 글이나 사이트가 자극적이고 저급한 글에 밀려 사라지게 되는 현상이나 좋은 아이디어나 프로그램이 상업주의에 밀려 없어질때 사용되곤 한다.(물론 단순히 변명으로 사용되기도 한다.)





Broken Window 혹은 깨진 유리창 이론은 사회학에서 유래한 용어이다. 깨진 유리창 이론(Broken Windows Theory)은 미국의 범죄학자인 제임스 윌슨과 조지 켈링이 1982년 3월에 공동 발표한 깨진 유리창(Broken Windows)이라는 글에 처음으로 소개된 사회 무질서에 관한 이론이다.

깨진 유리창 하나를 방치해 두면, 그 지점을 중심으로 범죄가 확산되기 시작한다는 이론으로, 사소한 무질서를 방치하면 큰 문제로 이어질 가능성이 높다는 의미를 담고 있다. 오랜 기간 수리하지 않고 방치된 창문 하나가 거주자들에게 버려진 느낌을 스며들게 한다. 담당자들이 그 건물에 별 관심이 없다는 느낌 말이다. 그래서 다른 창문이 하나 더 깨진다. 사람들은 이제 어지르기 시작한다. 낙서가 등장한다. 심각한 구조적 손상이 시작된다. 꽤 짧은 시간 안에 소유주가 그걸 고치려는 의지를 넘어설 정도로 건물이 손상되고, 결국 버려진 느낌은 현실이 되어 버린다.


이는 사람들이 쓰레기를 다른 쓰레기가 버려져 있는 곳에 버리는 것과 비슷한 이치이다. 길에 쓰레기가 하나도 없다면 처음으로 쓰레기를 버리고자 하는 사람은 망설여지지만 이미 쓰레기가 버려져 있다면 그다지 죄책감을 가지지 않는 다는 것에 기인한다.

깨진 유리창을 일소하듯이 경범죄 단속에 힘쓰는 것이 중범죄 예방에 효율적인 전략이냐에 대해서는 논란이 좀 있지만 Broken Window 이론은 리팩토링 논리의 바탕이 되는 이론이다. 

깨진 창문 - 즉 조악한 설계의 코드, 형편없는 경영상의 결정 등 프로젝트 기간 동안 팀이 동고동락해야 하는 것들 - 내리막길로 가는 첫걸음이다. 깨진 창문이 꽤 있는 프로젝트를 한다면, "나머지 코드가 전부 쓰레기니까 나도 그렇게 하지 뭐."라는 사고로 빠져들기 너무도 쉽다. … 같은 맥락에서, 코드가 청순할 정도로 아름다운(깨끗하고, 잘 설계되었으며 우아한) 프로젝트와 팀에 여러분이 속해 있다면, … 별도의 특별한 주의를 기울여서 엉망으로 만들지 않도록 노력할 확률이 높다. 비록 불길이 일어날지라도 (데드라인, 출하 날짜, 시사회 데모 등) 엉망진창으로 만드는 첫째 사람이 자신이 되는 것만은 피하려 한다. 그래서 지속적인 리팩토링이 필요하다는 논리이다.




물리학에서는 잘 알려진 엔트로피의 법칙이 있다. 엔트로피란 물질계의 열적 상태를 나타내는 물리량의 하나이다. 통계 열역학적 정의로 엔트로피는 열역학적 계의 통계적인 ‘무질서도’를 나타낸다.

열역학 제 1법칙은 에너지 보존에 관한 법칙이다. 즉 제 1법칙은 "에너지는 그 형태를 달리 할 수는 있으나, 없어지지는 않는다" 는 법칙이다

IT에서 주로 회자되는 원칙은 열역학 제 2법칙으로 우주의 진화 방향에 관한 법칙이다. 즉 제 2 법칙은 "자연계에 있어서 자발적인 진화 방향은 혼란도(또는 emtropy)가 증가하는 방향"이라는 법칙이다. 우주의 에너지는 일정하지만 변화는 계속되기 때문에 결국은 엔트로피가 더 이상 증가할 수 없는 극도의 혼란한 상황에 이르게 된다. 그렇게 되면 더 이상의 자연적인 변화는 불가능하다. 이것이 바로 열죽음이라고 부르는 우주의 종말이다.

이는 사람들이 bit의 조합은 코드는 영원히 순수하리라 기대하지만 실제 코드는 버그수정과 기능 추가등을 통해 부패한다. 따라서 코드의 부패를 막기 위해서는 임의적인 작업이 이루어져야 하며 이는 코드의 생존주기 내내 끝없는 리팩토링의 필요성의 근거이론으로 사용된다.



14세기 영국의 논리학자이며 프란체스코회 수사였던 오컴의 윌리엄에서 이름을 딴 오컴의 면도날이라는 원칙도 있다. 오컴은 보다 적은 수의 논리로 설명이 가능한 경우 많은 수의 논리를 세우지 말라. 라는 말을 자주했는데 이를 약간 변형하여 가장 단순한 것이 답이다. 라는 것으로 알려져 있다.

독일의 Bauhaus 운동의 아키텍트이자 리더인 Ludwig Mies van der Rohe(1886-1969)가 직접 오컴의 면도날 원칙을 참조했는지는 알수 없지만, 설계에 있어서의 최소주의 설계(minimalist design)라는 개념을 주장하였다.

그는 Less is More를 모토로 삼았는데 의미는 단순성(simplicity)과 명료성(clarity)이 좋은 설계를 만들게 된다라는 것으로 현대 설계의 아키텍처의 단순한 형태(style)와 관련된 용어이다.

SW 아키텍처에서는 견실한(consistent) 아키텍처는 동일한 것에 대해 수행하는 두가지 이상의 방법을 제공하지 않는다는 것을 의미하며, 이는 사용자로 하여금 어떤 것을 사용할지를 선택하도록 강요하는 시간 낭비를 유발시킬 수 있기 때문이다.
따라서, 견실한 SW 아키텍처는 배우기가 더 쉽고 빨라야 하며, 일단 처음에 배운 내용을 거의 알지 못한다고 하더라도 그 나머지에 대해서는 쉽게 예상할 수 있게 구성되어야 한다. 특별한 경우에 대해서 염두하고 처리할 필요가 없이 코드는 더 깔끔해지고 테스트 코드는 더 적어야 한다.



원칙까지는 아니지만 혹시 기타를 쳐본 사람들은 시나위의 기타리스트 신대철의 명언을 알것이다. "기타가 펜더면 뭐해? 손꾸락이 펜더여야지" (펜더(Fender)-Gibson 사와 더불어 세계 최고의 일렉기타 메이커)

이는 IT 세일즈의 주장과는 달리 좋은 툴을 든 바보도 그냥 바보다. 라는 말과 일맥상통한다. 이전에 적은대로 툴이든 방법론이든간에 기본적으로 어려운것을 쉽게 해주는 것은 무척이나 어렵다. (아주 가끔 일어나기는 한다.) 단지 복잡하거나 귀찮은 것을 단순하게 만들어 줄 수 있을 뿐이다.

이 주장이 조금 과격해지면 툴은 사람을 바보로 만든다 라는 주장에 쓰이기도 하는데 개인적으로는 그렇게까지는 생각하진 않고 좋은 툴이라도 바보를 똑똑하게 만들지는 못한다 정도로만 이해하는게 좋을 것 같다. 이는 소프트웨어 작업에서 가장 중요한 요소는 프로그래머들이 사용하는 도구나 기술이 아닌 프로그래머들 자신들의 자질(quality)임을 말해준다.


그 밖에도 IT에서 회자되는 타학문의 원칙들은 다음에.. ..






'Framework > 아키텍쳐 일반' 카테고리의 다른 글

예언?  (0) 2009.06.12
패키지의 설계 원칙  (0) 2009.06.08
AOP  (0) 2009.06.08
Self-Signed Java Applets  (0) 2009.06.01
여섯번째 계 - Encapsulation  (0) 2009.04.14
Posted by bleujin

AOP


AOP는 프레임워크나 아키텍쳐보다는 패러다임에 더 가깝기 때문에 굳이 AOP 전용 언어가 아니더라도 그 의미를 살리는데는 큰 무리가 없다.

이를테면 Javascript의 경우
AOP = {
 addBefore : function(obj, fname, before) {
  var oldFunc = obj[fname];
  obj[fname] = function() {
   before(arguments, obj);
   return oldFunc.apply(this,arguments);
  };
 },

 addAfter : function(obj, fname, after) {
  var oldFunc = obj[fname];
  obj[fname] = function() {
   result = oldFunc.apply(this, arguments);
   try{
    return result;
   } finally{
    after(result, arguments, obj);
   }
  };
 }
};

// example 1
   function setRed(o){
    o.style.color = "red";
   }

   function altBeforeAdvisor(args, targetObject){
    alert("before");
   }
   AOP.addBefore(this, "setRed", altBeforeAdvisor);

   // example 2
   function setBlue(o){
    o.style.color = "blue";
   }

   function altAfterAdvisor(result, args, targetObject){
    alert("after");
   }

   AOP.addAfter(this, "setBlue", altAfterAdvisor);

와 같이 비교적 쉽게 구현할 수 있다.

좀더 나은 예제는

에서 볼수 있다.


자바에서 AOP을 적용 하는 것은 생각보다 쉽지 않다. http://www.voelter.de/data/articles/aop/aop.html 와 같이 AspectJ언어를 사용하지 않는다면 말이다.

스프링처럼 AOP패러다임을 제공하는 프레임워크를 만들기 위해서는 약간의 테크니컬한 기술이 필요하다. 가장 쉽게는 데코레이터 패턴을 생각해 볼 수 있다. 이러 상황에서 상속은 좀 위험하기 때문에 현명한 선택이 아니다.

다소 복잡하지만 Proxy를 사용할 수도 있다.
먼저 Proxy객체는 InvacationHandler를 구현해야 한다.

package com.bleujin.thinlet.sample.invocation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class NumObjectHandler implements InvocationHandler{
  
  private Object target ;
  NumObjectHandler(Object target){
    this.target = target ;
  }

  public Object invoke(Object proxy, Method m, Object[] argsthrows Throwable {
    System.out.println(m.getName());
    return m.invoke(target, args);
  }
  
}

NumObjectHandler 클래스는 모든 메소드의 호출전에  System.out.println(m.getName());를 실행시켜 준다.


package com.bleujin.thinlet.sample.invocation;

import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;

public class TestInvoke extends TestCase {

  
  public void testSum() throws Exception {
    
    TestInvoke iv = new TestInvoke() ;
    
//    NumObject n3 = new NumObject(3);
//    NumObject n5 = new NumObject(5);
    
    NumObject no3 = new NumObject(3);
    NumObject no5 = new NumObject(5);
    
    INum n3 = (INum)Proxy.newProxyInstance(new SimpleClassLoader(""), no3.getClass().getInterfaces()new NumObjectHandler(no3)) ;
    INum n5 = (INum)Proxy.newProxyInstance(new SimpleClassLoader(""), no5.getClass().getInterfaces()new NumObjectHandler(no5)) ;
    
    iv.add(n3);
    iv.add(n5);
    
    assertEquals(8, iv.sum()) ;
  }
  
  
  private int sum() {
    int result = ;
    for (Object obj : data) {
      result += ((INum)obj).getValue() ;
    }
    return result;
  }


  private List<Object> data = new ArrayList<Object>() 
  private void add(INum numObject) {
    data.add(numObject;
  }
}


'Framework > 아키텍쳐 일반' 카테고리의 다른 글

패키지의 설계 원칙  (0) 2009.06.08
양화 구축  (2) 2009.06.08
Self-Signed Java Applets  (0) 2009.06.01
여섯번째 계 - Encapsulation  (0) 2009.04.14
중복을 볼 수 있는 눈  (0) 2009.03.13
Posted by bleujin

Generate a new keystore "testkey.keystore" as follows:

keytool -genkey -keyalg RSA -sigalg MD5withRSA -keystore testkey.keystore -alias testkey -validity 1460

Using this command, the keytool will generate a new keypair and create a self signed certificate for its public key. To create the certificate, the keytool will prompt for the necessary bits of X.509 information.

To sign the applet, use code similar to this (this code snipped was copied out of an Ant script):

<exec executable="${env.JAVA_HOME}/bin/jarsigner.exe">

    <arg value="-keystore"/>
    <arg value="testkey.keystore"/>
    <arg value="-storepass"/>
    <arg value="changeit"/>
    <arg value="-signedjar"/>
    <arg value="output.jar"/>
    <arg value="input.jar"/>
    <arg value="testkey"/>
</exec>

In order to run an applet signed with this key, the generated certificate may have to be imported into the Java plugin. For this, the certificate must be exported and stored in a file (e.g. 'testkey.cer'). The command line used to do this is:

keytool -export -alias testkey -file testkey.cer -keystore  testkey.keystore

The default keystore password is 'changeit'


Sharing Java objects between class loader instances
http://tom.conjective.ch/tomtom/space/Sharing+Java+objects+between+class+loader+instances

'Framework > 아키텍쳐 일반' 카테고리의 다른 글

양화 구축  (2) 2009.06.08
AOP  (0) 2009.06.08
여섯번째 계 - Encapsulation  (0) 2009.04.14
중복을 볼 수 있는 눈  (0) 2009.03.13
Here is Dragon  (0) 2009.03.12
Posted by bleujin