Many of these smells are a result of mismanaged dependencies. Mismanaged dependencies
conjure the view of code that is a tangled mass of couplings. Indeed, it is this view of
entanglement that was the origin of the term “spaghetti code”.

Object oriented languages provide tools that aid in managing dependencies. Interfaces
can be created that break or invert the direction of certain dependencies. Polymorphism
allows modules to invoke functions without depending upon the modules that contain
them. Indeed, an OOPL gives us lots of power to shape the dependencies the way we
want.


잘못된 코드는 객체의 의존관계 관리를 잘못한 결과물이라 할 수 있다. 
잘못된 의존관계는 복잡하게 꼬여서 coupling을 가지는 코드의 관점을 만들어 내고,
실제로 이것은 '스파게티 코드'라는 말의 어원이 되었다.

객체지향언어는 관계 관리에 도움을 주는 툴을 제공해준다.
인터페이스는 어떤 직접적인 관계를 방지해주도록 사용될 수 있다.
다형성(Polymorphism)은 모듈이 어떤 모듈에 대해 의존성 없이 기능을 사용할 수 있게 해준다
객체지향 어언는 우리가 원하는 형태의 의존 구조를 만드는데 도움을 준다.


Design Quality

What does it mean to be well designed? A system that is well designed is easy to understand,
easy to change, and easy to reuse. It presents no particular development difficulties,
is simple, terse, and economical.

잘 디자인(설계)된 프로그램은 위와 같은 특징들이 있음


Design Smells.

You know when a programmer is working with a poor design by the state of his eyes
and nose while he’s looking at the code. If his or her facial expression reminds you of the
detectives who have just opened a body bag containing a 12 day old corpse, then the
design is probably pretty ripe. The smells of a poor design have many different components.

1. Rigidity: The system is hard to change because every time you change one thing,
you have to change something else in a never ending succession of changes.

- 강직, 경직됨 : 시스템을 바꾸기 어려운 경우, 매번 바꾸었기 때문(스파게티 코드), 지속적인 변경이 필연적인 상황

2. Fragility: A change to one part of the system causes it to break in many other,
completely unrelated, parts.

- 단편, 깨지기 쉬움 : 하나의 변화 전체 시스템에 치명적 영향을 줌, Strongly Coupled~

3. Immobility: It is hard to disentangle the system into components that can be
reused in other systems.

- 부동성 : 시스템을 콤포넌트(다른 시스템에서 사용될 수 있는) 조합으로 분리해 내기 힘든 상황

4. Viscosity: The development environment is held together with scotch tape and
toothpaste. It takes forever to go around the edit, compile, test loop.

- 점착성 : 개발 환경에 지나친 의존성을 가지는 상황

5. Needless Complexity: There are lots of very clever code structures that aren’t
acutally necessary right now, but could be very useful one day.

- 불필요한 복잡성 : 당장 필요하지도 코드 구조가 존재하는 상황, 그러나 언젠가는 유용할 지도 모르는 코드임

6. Needless Repetition: The code looks like it was written by two programmers
named Cut and Paste.

- 불필요한 반복 : Ctrl C, V로 생산된 코드가 혼재하는 상황

7. Opacity: Elucidation of the originator’s intent presents certain difficulties
related to convolution of expression.

- 투명성 : 작성자의 의도에 대한 설명이 난해한 표현으로 어려움을 내포하고 있음
The primary mechanisms behind the Open-Closed principle are abstraction and polymorphism.
In statically typed languages like C++, one of the key mechanisms that supports
abstraction and polymorphism is inheritance. It is by using inheritance that we can
create derived classes that conform to the abstract polymorphic interfaces defined by pure
virtual functions in abstract base classes.

 inheritance를 이용하여 객체지향의 특성(polymorphism, abstraction)을 활용한다.

What are the design rules that govern this particular use of inheritance? What are the
characteristics of the best inheritance hierarchies? What are the traps that will cause us to
create hierarchies that do not conform to the Open-Closed principle? These are the questions
that this article will address.

상속의 사용에 있어 설계의 원칙이 있는가?
상속을 이용한 디자인의 특성은 무엇인가?

FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE
CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES
WITHOUT KNOWING IT.

Reference 형을를 쓰는 기능은 상속되는 클래스는 실제 Object의 구조를 알 필요없이,
상위 레벨의 클래스의 Object로 사용될 수 있어야 한다.

The above is a paraphrase of the Liskov Substitution Principle (LSP). Barbara Liskov first
wrote it as follows nearly 8 years ago1:

위의 문장은 LSP 정의의 의역이고 바바라 리스코프는 처음에 다음과 같이 섰다.(8년전)

What is wanted here is something like the following substitution property: If
for each object o1 of type S there is an object o2 of type T such that for all
programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T.

복잡하다.. -_-;


The Open-Closed principle is at the heart of many of the claims made for OOD. It is when
this principle is in effect that applications are more maintainable, reusable and robust. The
Liskov Substitution Principle (A.K.A Design by Contract) is an important feature of all
programs that conform to the Open-Closed principle. It is only when derived types are
completely substitutable for their base types that functions which use those base types can
be reused with impunity, and the derived types can be changed with impunity.

상위 class의 객체를 상속구조상의 하위 class의 객체로 대체할수 있어야 된다..
OCP 를 더욱 견고히 하기위한 원칙..



오늘 밴쿠버에서 태어난 학원 선생님이 명언을 남기셨다.

밴쿠버에는 2가지 계절이 있고,
2가지 계절이란,
There are August and Not August~ !!

밴쿠버에서 봄여름가을 까지 생활해본 사람으써 저말에 적극 동의한다..

캐나다는 7~8월을 제외하면 '우기'에 가까울 정도로 비가 오거나 흐린날이 많다..

물론 한국의 여름처럼 게릴라성 폭우 같은 비가 아닌,
부슬부슬, 안개비 같은것들을 자주 만날 수 있다..

맞아도 그만인 비만 오는 덕에, 비가 와도 거리에서 우산을 쓴 사람의 비율이 절반을 넘지 않는다..
비 많이 온다고 우산 꼭꼭 몇개씩 챙겨오는 사람들을 자주 보는데,
사실 와보면 알겠지만 아이러니하게 비는 자주오는데 우산이 필요없는 곳 임을 알게된다..

참고로 사진을 올린다.. 전체날씨중 70%는 저런 날씨라 보면된다..
[버나비 지역 주택가]

[다운타운]

[벤쿠버 남부 주택가]

우산 보다는 노*페이스 방한, 방수 자켓은 필수 아이템..
검정색 방수 자켓하나 있으면 봄여름가을겨울 지내는데 부족한게 없다.. 아주 유용하다..

이억만리 먼나라 답게 한국과는 또 다른 차이가,
6월경에는 새벽 4:30분이 되면 여명이 밝아오고 저녁 9시나 되어야 어두워 진다..

이것이 의미하는게 무엇일까..
해가 떠있는 낮의 길이가 무척 길기 때문에 생활패턴에도 조금 차이가 있다..

아침을 조금 늦게 먹는다거나 아침겸 점심으로 브런치 문화가 발달해 있고,
저녁도 7시 이후에나, 조금 늦게 먹는거 같다..
긴 오후시간을 이용해 여가시간을 즐기는 사람도 많다..
회사-집-회사-집 패턴이 아니라 회사-여가-집의 패턴이 가능한 것..
물론 10월 정도되면 해도 7시에나 뜨고 저녁 7시만 지나도 어두워진다..
한국과 대충 비슷해지는 시기가 아닌가 싶다..

여름기온은 최대 30도 정도이고,
날씨가 건조하기 때문에 햇살은 따갑지만, 전혀 땀이 나지 않는다..

동양인은 이런 강한 햇살에 취약한 피부를 가졌기에,
남미나 유럽쪽 사람들에 비해 훨씬 피부가 잘 타기 때문에, 선크림, 모자는 필수이며,
선글라스나 자외선 차단 코딩된 안경이 필수다..
없으면 눈이 굉장히 피곤하다..

봄과 가을은 10~20도 수준으로 집안에서는 반팔과 반바지만 입고도 생활이 가능한 수준이나,
10도 이하로 떨어지는 새벽에는 많이 춥기때문에 편하게 입을 옷은 반팔, 긴팔 일단 다 챙기는 것이 좋다..

겨울은 생활하지 않아서 잘 모르겠으나,
벤쿠버는 역시 눈보다는 비가 많이 온다 그런다..
한국보다 겨울은 따듯하고 여름은 시원한 이상적인 날씨다..

그리고 신기한게, 밴쿠버에는 여름에도 모기가 없다.. (한번도 물려 본적이 없음)
모기 물린데 바르는 약따위 필요없으니 참고하시라..

벤쿠버 날씨는 이것만 알고 가면 일단 OK..
 

 
 Crawler에 의해 추출된 웹페이지를 적절히 Indexing 하기 위해서는 해당 문서에 본문영역을 추출(Filtering)하는 것은 검색의 정확도에 있어 매우 중요한 부분이다.
 
 관련된 연구논문에 따르면 본문과 상관없는 네비게이션, 광고, 페이지 템플릿 등 본문의 내용과 관련없는 부분이 전체 Html 구성 Text에서 40% 이상을 차지한다 그런다. 물론 광고, 네비게이션, 템플릿등은 모든 페이지에서 공통적으로 등장한다면 단순하게 TFIDF과 같은 단순한 확률분포기반 Indexing 만으로도 어느정도 검색과 무관한 데이터로 가려낼 수 있기는 하지만, 이외의 여러가지 다양한 용도로 사용될 수 있고 검색의 방법론 등을 고려해 보았 을때 매우 중요한 기능 임에는 틀림없다.

 일단 아래의 신문기사의 DOM Tree를 보자.

다소 길기는 하지만 본문 영역만 자세히 보자.

 본문을 추출하기 위한 가장 간단한 아이디어는 Dom Tree 를 이용하여 Link(a tag)가 설정되어 있지 않는 Text Node를 추출하는 방식이 있으나 이렇게 했을때 불필요한 텍스트를 포함하고 있을 가능성이 매우 크기때문에 2차적인 보정이 필요하다. 
 
아래는 본문영역 추출을 위해 사용될 수 있는 간단한 방법이다. 
(몇몇 논문 참조 및 개인연구를 통해 알아낸 가장 적합한 방법이라 생각한다)


1. Unlink Text Node를 추출하여 Dom의 Node Depth가 같은 Node의 군집을 추출한다.
 (실제 구현시에는 인접노드 추출후 재병합하는 방법을 사용하였는데, 이것은 이후에 설명을 하겠다) 

2. Unlink Text의 밀도를 추출하여 대량의 Text가 밀집된 영역군을 추출하고 1번에서 추출된 후보군집과의 비교를 통해 최종 추출 노드들의 공통 parent node들을 추출하고 parent의 child내의 Text Node의 조합이 본문이 된다. 여기서 추출된 parent node는 1개 이상이 된다. 덧글과 같은 비링크 데이터가 존재할 수 있기 때문이다. 그래서 다수의 후보 node 군이 추출된다.

3. 기사의 내용이 지나치게 짧다거나 기사보다 내용이 긴 덧글 등은 본문 추출의 최대의 적이다.
  - 사실상 짧은 내용의 기사일 경우 정확한 추출은 불가능하다. (여러 페이지 분석을 통한 템플릿 추출 이후 가능)
  - 덧글 등은 Table 태그의 구조적 Pattern 양상을 보이므로, DOM 의 구조분석을 통해서 덧글 여부를 판별 할 수 있다.

4. 가장 이상적인 방법은 언론사 사이트의 메인페이지에서 동일 level(군집)의 Link를 추출하여, 동일한 페이지 구조 템플릿을 사용하는 페이지들을 분석하여, 본문에 해당되는 DOM Tree의 Path를 추출하는 방법이다.


 한가지 불행한 사실은 본문과 그렇지 않은 부분의 경계가 모호한 페이지도 많다는 것이다. 연구를 하다가 부딛히는 문제들중 본문의 기준을 무엇으로 볼것인가 라는 의문을 던져주는 사이트도 적지 않았다.
 
 결국에는 패턴을 통한 기계의 자동화와 인간의 인지에 의한 영역 지정, 기계와 인간의 하이브리드된 방법이 답니다. 다시말해 추출의 대상이 되는 사이트의 XPath 형태의 본문을 포함하는 영역의 DOM 경로 추출 후 추출 값이 정확하지 않을 경우 추가 되거나 제거되어야 할 영역의 규칙을 사람에 의해 수동으로 설정이 가능하도록 하는 방법이 지금으로써는 답이다.

 물론 불과 몇년전에 불가능으로 보였던 것들에 대한 구현들에 관한 내용이 최근에 계속해서 논문으로 나오고 있는 것을 보면, 수년내에 자동 본문 추출도 완전에 가까운 형태가 되지 않을까 생각된다. 아니면 사람이 좀 고생하는 거고..

김영곤 <gonni21c@gmail.com>
 
 Html Parser는 검색엔진 개발에 있어 가장 기본적이면서도 문자열의 논리적 구성을  담당하는 역할을 하는 중요한 모듈이므로, 속도, 안정성, 정확성등 여러가지 섬세하게 고려해야 될 부분이 많다.
 학생시절 부터 관심이 있어서 Html Parser를 어떻게 만들면 잘 만들수 있을까 참 많은 고민을 한 것 같다.
많은 세월이 흘렀음에도 아직도 해답을 찾지는 못했고, 대략적인 실패한 경험들을 토대로 개발시 고려사항들을 정리해 보고자 한다. (참고로 Version 6 정도의 Html Parser가 현재 개발중이다.)


1. HTML파서와 XML파서의 제작은 차원이 틀리다.
 
 XML 문서와 같은 경우 Well-Formed check를 통해 Tag의 열고닫음, XML의 문법적인 유효성을 강하게 체크하지만,
HTML은 그렇지 못하다. 대표적으로 <BR> 과 같은 Tag는 </BR>과 같이 닫기Tag가 명시적으로 사용되고 있지는 않으므로,
HTML Dom Tree 생성시 계층구조 구성을 위한 별도의 처리가 필요하다.
 
 즉, HTML은 모호성(Ambiguous)을 아직까지는 문법적으로 허용하며, 문법의 정확성을 보장할수 없는 변칙적인 언어이다.
대부분의 HTML DOM 파서는 1Pas 시 Well-Formed 조건에 맞게 XML 파서로 파싱이 가능한 형태의 문서를 생성하고, 
2Pass시 Dom Tree를 생성하는 구조로 되어있다. 



2. 속도와 공간사용량 타협이 필요하다.

 대부분의 Parser가 아마도 최단 시간내에 Parsing을 완료하는 것을 목표로 할 것이다. 물론 정확하게 파싱해 내는 것은 말 할 것도 없다. 하지만 파서의 속도가 아무리 빠르다 할지라도 Web 상의 정보, 소스를 Network을 통해 가져오는 Html Parser의 특성상 극단적으로 속도를 빠르게 하는 것이 무의미한 작업일 수도 있다.

 하지만 속도가 극단적으로 중요한 상황이라면 네트웍에서 데이터를 가져오는 동안 소스의 부분을 여러부분으로 나누어 독립적인 쓰레드의 병렬 파싱을 하도록 한는것도 가능하지만, 가장 간단하면서도 빠른 방법은 일단 데이터를 웹에서 다운받아 StringBuffer나 Char의 배열에 미리 저장해 두고 parsing하는 것이 속도 측면에서는 더 효율적이 이었다.

 파싱을 위한 초기작업으로 HTML 문서에서 의미의 단위별로 텍스트의 조합, 즉 Token을 추출하는 Lexing 작업이 선행된다. 파서자체, 객체지향 프로그램 언어적 시각으로 보았을때 파싱의 대상이 되는 Source라는 커다란 객체를 문법으로 정의된 의미의 객체단위로 분리하는 작업이라 할 수 있다. 보통의 경우 Token의 Text와 Token Object가 최소한 1:1의 관계를 이루게 된다. 모든 Parser가 그러하듯 Lexing의 가장 작은 단위동작은 char, 즉 문자 하나를 소스로 부터 가져와 비교하고 그 문자 하나하나가 모여서 하나의 문자열 Token을 이루게 된다. Char단위의 문자열 연산이 상당히 많으며, String 연산 또한 많다. Token의 Size는 예측할 수 없으므로 결국 Token값의 최종결과는 정적으로 할당된 배열에 저장하게 되는데, 토큰 생성시 마다 정적배열의 생성은 극단적으로 H/W power가 약한 시스템에서는 무리가 될 수 있으며, 배열의 전체공간이 Pull인 경우 배열공간을 갱신하는(ex, StringBuffer) 자료구조를 쓸 경우 속도적인 측면에서 손해를 보게 된다.

언제나 그렇듯, 속도와 공간의 타협점을 찾아야 한다.



3. 사용언어의 추출 : UTF-8, EUC-KR

 다양한 언어가 공존하고, Charset이 다양하게 존재하는 웹의 구조적 특성상, 웹에서 어떤 데이터를 취득했을때, 그 데이터가 어떠한 Charset을 사용했는지 알아내는 것이 HTML 문서 파싱의 선행 작업이라 할 수 있다. 어떠한 Charset을 사용하는지는 아래와 같은 단서를 이용해서 알 수 있다.

a. Http Protocol Header의 Charset attribute 값에서 추출
b. Html Header 내의  <meta http-equiv="Content-Type" content="text/html; charset=euc-kr">
c. <Html lang="ko"> Tag의 lang 속성

a의 경우 서버 자체적으로 제공해 주는 기능이라 최초 Lexing전 charset의 정보를 취득할 수 있으며, b, c의 경우 일단 데이터를 저장하고 pre-reading을 통해 해당정보를 추출해야 한다.(Parsing 속도 측면에서 좋은 방법은 아니며, 초기 ASCII 모드형태로 데이터를 읽으면서 해당정보가 추출된 이후 charset 형태의 맞추어 문자 읽기를 동적으로 변환하는 방법으로 가능하다)

저러한 정보를 얻을 수 없는 최악의 상황은 byte연산을 통해 현재 어떠한 type의 문자를 포함하고 있는지 별도의 연산과정이 더 필요하다.



4. 무한 루프의 가능성

 앞서 언급한 바와 같이 html 언어는 모호성을 허용하며, 변칙적인 문법의 사용이나, 정확하지 않은 문법을 사용한 문서들도 상당히 많음을 항상 염두해 두어야 한다. 아마도 모든 파서가 데이터로 부터 커서나 포인터를 증가시켜 문자를 읽어들이면서 파싱이 진행되는 형태를 취하고 있을 것으로 생각된다. 
 
 커서의 진행방향이 consume만 하는, 값이 증가만 하는 단방향성만을 가진 다면 큰 문제는 없으나, 경우에 따라서는 커서의 backward가 필요한 경우가 종종 발생한다. 생각해 볼수 있는 가장 심각한 시나리오는 커서가 진행을 못해 파싱 과정에서 파서 자체의 프로세스가 무한루프에 빠지는 경우이다.

 커서가 지속적으로 진행하는 단방향성을 가지는 것이 최고의 시나리오이나 그렇지 못한 문법구조를 가졌다면 무한루프의 가능성을 항상 염두해 두어야 되겠다. (불행히도 수많은 Case에 대한 Test 필요)



5. 주석처리상의 문제

 위에서도 언급한 바와 같이 HTML parser의 문자처가 단방향성을 가진다면 더할 나위 없이 좋겠지만, <script>, <style> <!-- 주석 -->등 과 같은 요소로 인해 사실상 커서의 backward나 미리 읽기버퍼를 현재 문자이후에 등장하는 문자열을 검사해야 하는 경우가 발생한다. 

 <script>나 <style> 태그의 경우 Value값의 경우 HTML과는 다른 별개의 언어의 문법을 사용하므로 HTML Parsing 모듈이 해당 언어를 파싱할 수 있는 모듈에게 제어권을 넘기거나 혹은 </script> 나 </style>이라는 문자열이 나타날때 까지 모든 문자열을 무시해야 한다. 주석의 경우에도 마찬가지 인다.
문자 단위로 커서를 이동하면서 미리읽기를 통해 이후에 뒤따르는 문자열을 분석하는 작업이 필요하다. 속도 측면에서 상당한 마이너스가 아닐까 생각된다.



6. DOM Object의 경량화

 앞서 언급한 바와 같이 Token 과 Token의 Object는 1:1 관계를 이룬다. 일반적으로 Java Application이 실행시 부하량(시간지연)에 가장 많이 영향을 주는 요소는 객체의 생성 시간이다. 객체의 생성량이 크지 않는 경우라면 시스템 성능에 미치는 영향이 미미하겠지만 대형 포털 사이트 메인페이지를 파싱하면 보통 2000개 이상의 Token이 생성된다. Token Object 객체도 똑같이 2000개 이상 생성된다는 소리이며, Tree 생성시 구조에 따라서 생성 객체수는 2배이상 증가 될 수 있다. 

 일반적인 프로그램에 비해 객체의 생성량이 많고, 페이지의 특성에 따라 생성되는 수를 예측할 수 없기 때문에, 일반적으로 Class 생성시 DOM을 구성하는 단위 객체는 최대한 경량화 되어야한다. C의 구조체처럼 데이터만을 포함될 수 있는 형태가 적절하며 연산이 들어간 method의 사용은 최소화 하는 것이 바람직해 보인다.

 전체 디자인적으로는 Parsing 을 하는 행위모듈과 파싱후 생성되는 객체, 데이터 모듈을 Class 단위로 철저히 분리해야 한다. 또한 Parsing의 행위모듈의 경우에도 최대한 static 하거나 single object에서 실행되도록 하여, 객체의 생성 자체를 최소화 하는 것이 바람직한 방향으로 보인다. 

 객체의 생성이 과도하다면 Object의 메모리 영역을 clone copy를 통해 새로운 객체를 생성하는 Prototype Design Pattern을
고려해 볼 수 있으나, Prototype 패턴은 사용 class의 구조적 특성에 따라 효율이 오히려 떨어지는 경우도 있다. 객체 생성시 생성자의 구현이 복잡하다면 고려해 볼 수 있으나, 생성자의 내용이 없는 DOM의 Token 혹은 Node Object 생성시에는 시간적 효율은 거의 없거나 증가하는 양상을 보였다.



7. 문법 확장성의 고려
 새로운 문법에 대한 확장성에 대한 고려도 물론 필요하다. 전체 설계시 영향을 주는 부분이고, 지나친 환장성은 속도, 공간적 효율을 저해한다. 이 부분은 현재 개발중인  YGHtmlParser Release시 별도 언급을 하겠다.


- 김영곤(gonni21c@gmail.com) 

+ Recent posts