'Framework/아키텍쳐 일반'에 해당되는 글 23건

  1. 2009.02.11 Method Design
  2. 2009.02.10 몇가지 프로그래밍 조언
  3. 2009.01.16 Bleujin Framework 활용 : HTML Parsing



1. null이 아닌 길이가 0인 배열을 리턴하라.
메소드나 클래스나 좋은 디자인의 제 1원칙은 자신 보다는 호출자를 배려한 코드이다. 결국 좋은 메소드 디자인이란 메소드 그 자체에 있다기 보단 그 메소드를 호출하는 쪽에서 판단하는 것이다.

# Bad
public Cheese[] getCheeses() {
   if (cheeseInStock.size() == 0) return null ;
   .......
}
위 코드가 좋지 못한건 호출하는 쪽에서는

# Bad 호출하는 쪽
Cheese[] cheeses = shop.getCheeses() ;
if (cheese != null) {
    for (int i=0 ; i < cheeses.length ; i++) {

    }
}

와 같은 별도의 확인 작업을 해야하는 문제가 있다.

# Good
public Cheese[] getCheeses() {
   return (Cheese[]) cheesesInStock.toArray(new Cheese[0]) ;
}

만약 메소드를 위와 같이 작성했다면

# Good 호출하는 쪽
Cheese[] cheeses = shop.getCheeses() ;
for (int i=0 ; i < cheeses.length ; i++) {

}

null은 가능하면 리턴값으로 사용하지 않는게 좋다
.. 라는건 사실 아주 조그마한 것이지만 좋은 디자인이란 Outer에서 판단된다는 걸 잊어서는 안된다.





2. 디미터 함수 법칙

Case 1)
public void plotDate(Date aDate, Selection aSelection) {
    TimeZone tz = aSelection.getRecorder().getLocation().getTimeZone() ;
    ......

}

언뜻 별다를게 없어 보이는 위 코드는 디미터 함수의 관례를 위반하였다.

디미터 함수 법칙은 최소 지식의 원칙(Principle of Least Knowledge) 이라고도 불린다.(프로그램에서 모듈간 결합도를 최소화하려 시도한다.) OO 프로그래밍 연구 프로젝트(Demeter Project)에서 나온 법칙으로 흔히 '친한 친구들 하고만 이야기 하여라(Only talk to your immediate friends)' 라는 이야기로 요약된다. 친구는 클래스 정도로 해석하면 되며, getA().getB().getC() 하는 식으로 '여러 객체에 물어물어 가는 식으로 정보를 찾아내는 스타일은 자제하는 것이 좋다'라는 법칙이다. 

재미있는 것은 다른 법칙과 달리 디미터의 법칙은 장/단점이 생기는 법칙으로, 디미터의 법칙을 잘 따를 경우 메소드(=함수)가 많이 생기게 되면서 복잡도가 증가 할 수 있다. 하지만, 전체적으로는 잘 따르는 것이 좋은 법칙이다. 현재 디미터의 법칙이라 불리는 법칙은 디미터 프로젝트의 OO 프로그래밍 스타일 가이드 중 함수와 메소드(Law of Demeter for Functions/Methods) 부분의 가이드를 말한다.


사실 소프트웨어에서 법칙이라고 말하는게 조금 웃기긴 하지만
디미터 함수 법칙에 따르면 모든 메서드는 다음에 해당하는 메소드만을 호출해야 한다.

class Demeter {
   private A a = new A();

   private void func() {...}

   public void example(B b){
      C c = new C();
      Int f = func() ;     // 자신의 메소드
      b.invert() ;         // 메소드에 넘어온 인자
      a.setActive() ;   // 자신이 생성한 객체
      c.print() ;          // 직접 포함하고 잇는 객체
   }
}

디미터 함수 법칙을 가능한 지키려고 하면서 한 서브 프로젝틀를 리팩토링한 경험이 있는데 생각보다 훨씬 효과가 좋았다. 최근의 툴은 .을 누르면 해당 객체의 메소드가 대부분 자동으로 완성되기 때문에 무심코 사용하곤 하는데 aSelection.getRecorder().getLocation().getTimeZone() ; 식의 방법은 불필요한 관계를 맺음으로써 유지보수 하기 어려게 만들곤 한다. 




3. 인자의 유효성을 검사

public void AnonyMethod(AnonyClass ac) {
   Assert.notNull(ac) ;


    ac.fn() ;
    .............
}

이러한 방식을 방어적인 프로그램이라고 할 수 있다.

다만 일반적으로 봤을때 인자의 유효성을 호출자가 체크해야 하는가 아니면 위처럼 메소드 자체가 체크해야 하는가에 대해서는 개인적으로 아직 결론을 내리지 못했다.(섞어 쓰고 있다-ㅅ-) 위와 같은 방어적 프로그램은 메소드를 본래 의도와 상관없이 복잡하게 만들 가능성이 있기 때문이다.

에펠같은 계약에 의한 프로그래밍(PBC) 언어에서는 선조건과 후조건을 설정 가능하도록 되어 있는데 자바도 그 특징을 조금씩 도입하고 있다. 앞에서 말한바와 같이 좋다라는 판단은 호출자가 내리지만 잘못 사용하는건 내탓이오 라는 걸까.. 왠지 사회운동을 보는 것 같아 우습다 -ㅅ-











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

Class Design  (0) 2009.03.07
나쁜 디자인의 징후  (0) 2009.02.22
Design Principle - SRP  (0) 2009.02.22
몇가지 프로그래밍 조언  (0) 2009.02.10
Bleujin Framework 활용 : HTML Parsing  (0) 2009.01.16
Posted by bleujin


몇가지 프로그래머책에서 뽑아봄.


1. prototyping은 배움의 경험이다. prototyping의 가치는 만들어낸 코드에 있지 않고, 작성하면서 배운 교훈에 있다.



2. 요구사항을 수집하지 말고 채굴하라. 

흔히 요구사항은 바닥에 널려져 있고 단지 수집(Gethering)하는 것이라고 착각하지만 보통의 요구사항은 가정과 오해, 그리고 정치의 지층들 속에 깊이 묻혀져 있다.

그리고 요구사항은 최대한 일반적 진술로 만들고, 정책에 대한 정보는 구현에서 지원해야 할것들의 예시로 넘겨주는게 좋다. 이를테면 '해당 직원의 관리자와 인사부에서만 그의 기록을 열람할수 있다. '라는 건 요구사항이 아니다. 이를 일반적 진술로 바꾸면 "권한이 부여된 사용자만이 직원기록에 접근할 수 있다." 이고 그 정책 예로 위 말을 언급할 수 있다. 이를 좀더 추상적으로 말하면 프로그래머는 접근관리 즉 권한관리 시스템을 설계해야 한다는 뜻이다.

다음으로 요구사항은 바람과 모래와 별들의 생텍쥐페리의 말대로 완성이라는 것은 더 이상 더할것이 없을때가 아니라, 더 이상 빼낼 것이 없을때 얻게 되는 것이다.

요구사항과 관련하여 마지막으로 할 말은 사용자들이 어떤 작업을 현재 어떻게 하느냐는 것을 알아내는 것보다, 왜 그걸 하는지 그 내재적 이유를 알아내는 것이 더 중요하다. xp의 실천 원칙처럼 사용자처럼 생각하기 위해서는 사용자와 함께 일하라



3. 관련없는 것들은 서로 관련없도록 하라.

기하학에서 두 직선이 직각으로 만나는 경우 직교한다고 말한다. 컴퓨팅에서 이 용어는 일종의 독립성(independence)나 결합도 줄이기(decoupling)를 의미한다. 그게 메소드가 됐든 클래스가 됐든 컴포넌트가 됐든 프레임워크가 됐든 자족적이고, 독립적이며, 단하나의 잘 정의된 목적만 갖도록 설계하라. 

직교성의 장점으로 첫번째로 생산성 향상을 들수 있다. 
   - 변화가 국소화되어 개발시간과 테스트 시간이 줄어든다.
   - 직교적인 접근법은 재사용을 촉진한다 (시스템이 더 느슨하게 결합되어 있을 수록 재설정하고 리엔지리어링하기 쉽다.)
   - 직교적인 컴포넌트들을 결합하는 경우 미묘한 생산성 향상이 있다. (M*N)

두번째로 직교성은 리스크를 감소시킨다.
   - 감염된 코드는 격리된다.
   - 시스템이 잘 깨어지지 않는다.
   - 써드파티 컴포넌트들로 연결되는 인터페이스들이 전체개발의 작은 부분에 한정되기 때문에 특정 벤더나, 플랫폼에 덜 종속될 것이다.



4. Select 는 망가지지 않았다.

OS나 컴파일러의 버그를 발견하는 일은 정말 드물게 일어나며, 심지어 써드파티 제품이나 라이브러리일지라도 드문일이다. 버그는 애플리케이션에 잇을 가능성이 가장 크다.

디버깅 사고방식
   - 가장 속이기 쉬운 사람은 자기 자신이다.(에드워드 블워 리톤) - 고무오리
   - 디버깅할때 당황하지 마라.(근시를 조심하라)
   - select는 망가지지 않았다. OS는 아마 망가지지 않았을 것이다. 데이타베이스도 아마 괜찮을 거다.

디버깅의 순서는 첫번째가 버그 재현이다. 일단 재현이 되지 않는 버그는 무척 해결하기 어렵다. 
두번째 버그를 분리해야 한다. 버그를 일으키는 프로세스의 최소 동작 집합을 만드는 걸 말한다. 
세번째 데이타를 가시화한다. 버그를 일으키는 프로세스에서 일어나는 값의 변화를 추적하고 트레이싱 하여 실제 버그를 이때 발견하게 된다. 
네번째 버그의 증명이다.
다섯번째가 가장 중요한데 새 테스트 코드를 추가하여 같은 버그는 한번만 잡도록 한다.




5. 일찍 작동을 멈추게 하라.

보통은 죽은 프로그램이 절름발이 프로그램보다 해를 훨씬 덜 끼친다.

모든 에러는 정보를 준다. 에러가 발생할리 없다고 스스로를 설득하고선 그걸 무시하기 보다는 만약 에러가 있다만 정말로 뭔가 나쁜일이 생긴 것이라고 자신에게 이야기 해야 한다.



6. 시작한 것은 끝내라

가능하다면, 리소스를 할당한 루틴이나 객체는 해제도 자기가 책임져야 한다.

private void readCustomer(final String fName, Customer cRec){
   cFile = fopen(fName, "r+") ;
   fread(cRec, sizeof(cRec), 1, cFile) ;
}

private void write Customer(Customer cRec){
   rewind(cFile) ;
   fwrite(cRec, sizeof(cRec), 1, cFile) ;
   fclose(cFile) ;
}

void updateCustomer(const String fName, double newBal){
   Customer cRec ;
   readCustomer(fName, cRec) ;
   cRec.balance = newBal ;
   writeCustomer(cRec) ;
}

위의 updateCustomer 함수는 read 호출 - value없데이트 - write 호출로 이루어져있고 별 문제 없어보이지만 깨지기 쉽다.  
정책상의 변화로 newBal이 0보다 큰경우에만 수정이 가능하도록 바꼈다고 해보자

void updateCustomer(const String fName, double newBal){
   Customer cRec ;
   readCustomer(fName, cRec) ;
   if (newBal >= 0.0) {
        cRec.balance = newBal ;
        writeCustomer(cRec) ;
   }
}
와 같이 무심코 바꾼 코드는 심각한 문제를 내포하게 된다. 논리상으로는 그럴듯해보이지만 newBal < 0 일때는 fread만 발생하고 fclose를 하지 않아 리소스의 손실이 발생하게 된다.

정확히 바꿔야 한다면 아래와 같이 바꿔야 한다.

void updateCustomer(const String fName, double newBal){
   Customer cRec ;
   readCustomer(fName, cRec) ;
   if (newBal >= 0.0) {
        cRec.balance = newBal ;
        writeCustomer(cRec) ;
   } else {
       fclose(cFile) ;
   }
}

하지만 그리 깔금해 보이지 않는다.

그보다는

private void readCustomer(File cFile, Customer cRec){
   fread(cRec, sizeof(cRec), 1, cFile) ;
}

private void write Customer(File cFile, Customer cRec){
   rewind(cFile) ;
   fwrite(cRec, sizeof(cRec), 1, cFile) ;
}

void updateCustomer(const String fName, double newBal){

   File cFile = fopen(fName, "r+");
   Customer cRec ;
   readCustomer(fName, cRec) ;
   if (newBal >= 0.0) {
        cRec.balance = newBal ;
        writeCustomer(cRec) ;
   }
   fclose(cFile) ;
}

와 같이 애초에 updateCustomer 함수가 리소스를 할당하고 해제하는 역할을 같이 담당하는게 좋다. 처음 예처럼 리소스를 할당하는 부분과 해제하는 부분을 각기 다른 메소드에 넣는다면 잠재적인 에러를 유발시킬 수 있다.



7. 통합하지 말고 설정하라.

비지니스 로직, 법률, 경영자의 취향 등을 수용하느라 코드를 변경할 때마다 새로운 버그가 시스템을 깨드릴수 있는 위험을 낳게 된다. 그러므로 세부사항에서 벗어나자. 아무리 뛰어난 천재라도 세부사항에 집착하면 그 재능이 발휘되지 않는다. (머피의 제 8법칙)

우선 시스템을 되도록 설정 가능하게 만들자, 배경색, 프롬프트 텍스트 뿐 아니라 알고리즘의 선택, 사용할 데이타베이스 제품, 미들웨에 기술, 사용자 인터페이스 스타일, 사용자 선호사항, 설치 디렉토리 등을 말이다.

이러한 방식을 메타데이터 주도 애플리케이션이라고 하는데 코드에서는 추상화를, 메타데이타에는 세부 내용을 (구체적인 것보다 추상적인것이 더 오래간다.) 담는다. 요구사항처럼 프로그램은 최대한 일반화 해서 만들고, 세부사항들은 가능하면 컴파일된 코드 기반 바깥으로 빼라.

이 방식은 아래와 같은 장점이 있다.
   - 설계의 결합도를 줄여 좀더 유연하고 적응성 있는 프로그램을 만들수 있다.
   - 세부사항을 코드 밖으로 몰아냄으로써 보다 강하고 추상적인 디자인을 만들수 있다.
   - 애플리케이션을 커스터마이징 하기 위해 다시 컴파일 할 필요가 없다.
   - 메타데이타는 범용 프로그래밍 언어보다 문제 도메인에 가까운 방식으로 표현될수 있다.
   - 동일한 애플리케이션 엔진과 상이한 메타데이타를 이용해 여러 다른 프로젝트를 진행할 수 있게 된다.





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

Class Design  (0) 2009.03.07
나쁜 디자인의 징후  (0) 2009.02.22
Design Principle - SRP  (0) 2009.02.22
Method Design  (0) 2009.02.11
Bleujin Framework 활용 : HTML Parsing  (0) 2009.01.16
Posted by bleujin

굳이 메쉬업 프로그래밍을 하지 않더라도 하다보면 인터넷에서 자료를 쭉 긁어오고 싶을때가 있다.
이건 사실은 신문사 만화 사이트를 일일이 클릭해가면서 - 게다가 그 수많은 광고를 참으며 - 보느니
로컬에 그림만 다운로드 받아봐야지 하고 후다닥 만들었다-ㅅ-


이 작업을 하려면 먼저 bleujin Framework core의 jar 파일을 아래 포스팅에서 다운로드 받고..


위 3개의 jar가 추가적으로 필요하다. 예전에 작성할때 다운로드 받은거라 최근에는 버전업이 됐겠지만 그냥 쓴다-ㅅ-
아마 common-io랑 common-lang도 필요하겠지만 그건 그냥 apache 에서 다운로드 추천-ㅅ-
(오늘까지 방문자가 한명도 없으니 다소 무책임해도 상관없으리라.. 쿨럭)

Framework에는 html Parsing에 관련된 - jericho의 소스에 Facade 패턴을 사용해서 간단하게 만들 수 있었다. - 부분만 있어서 먼저 인터넷에 접속해서 InputStream을 받아오는 로직을 작성해야 한다.



public class Spider {

 private Logger log = LogBroker.getLogger(Spider.class) ;
 
 public Reader getPageContent(String httpURL) throws HttpException, IOException {
  String content = IOUtils.toString(getInputStream(httpURL), "EUC-KR") ;
  return new StringReader(content) ;
 }

 public InputStream getInputStream(String httpURL) throws HttpException, IOException {
  HttpClient httpclient = new HttpClient();
  GetMethod httpget = new GetMethod(httpURL);
  try {

   httpclient.executeMethod(httpget);
   log.info("recevied data : " + httpURL) ;
   
   InputStream input = httpget.getResponseBodyAsStream() ;
   InputStream result = new ByteArrayInputStream(IOUtils.toByteArray(input)) ;
   input.close() ;
   return result ;
   
  } catch (Exception ex) {
   log.warning(ex.getMessage()) ;
   throw new HttpException(ex.getMessage(), ex) ;
  } finally {
   httpget.releaseConnection() ;
  }
 }
}

EUC-KR의 상수 부분이 걸리긴 하지만 머 대충 만들고 넘어가자-ㅅ-(사실 사이트를 제대로 만들었다면 EUC-KR은 요즘의 국제화 시대에 맞지 않는 인코딩방법이다. )

사용은 대충 아래와 같이 한다.

 public void testSportChosun() throws Exception {
  String httpURL = "http://manhwa.sportschosun.com/enter/cartoon/juyu/cartoon_juyu2.asp?num=138&title=juyu&Direct=Next" ;
  Spider s = new Spider() ;
  Reader content = s.getPageContent(httpURL) ;
  HTag tag = HtmlParser.parse(content) ;
  
  HTag img = tag.findElementBy(IMG, "src", "http://manhwa.sportschosun.com/enter/cartoon/juyu/image_cartoons/20071119y_233.gif") ;
  System.out.println(img.toString());
  System.out.println(img.getPath());
  
  
  String imgPath = img.getAttributeValue("src") ;
  System.out.println(imgPath);
  
  content.close() ;
 }


 public void testSave() throws Exception {

  int current = 100 ;
  Spider s = new Spider() ;
  for (int i = 1; i <= current; i++) {
   String path = "http://manhwa.sportschosun.com/enter/cartoon/juyu/cartoon_juyu2.asp?num=" + i + "&title=juyu&Direct=Next" ;
   Reader content = s.getPageContent(path) ;
   HTag tag = HtmlParser.parse(content) ;
   HTag img = tag.getChild("body[0]/table[4]/tr[0]/td[2]/table[1]/tr[8]/td[0]/img[0]") ; // /html/body/table/tr/td/table/tr/td/img
   String imgSrc = img.getAttributeValue("src");
   
   FileOutputStream output = new FileOutputStream(new File("c:/temp/" + StringUtil.substringAfterLast(imgSrc, "/")));
   IOUtils.copy(s.getInputStream(imgSrc), output) ;
   output.close() ;
  }
 }


HTag가 클래스가 Facade 클래스인데 이런저런 다양한 파싱에 관련된 메소드들을 모두 가지고 있다.

이를테면 getChild("body/table[4]/tr[0]/td[2]/table[1]/tr[8]/td[0]/img[0]") 는
body안에 5번째 table 태그안에 첫번째 tr안에 3번재 td안에 2번째 table안에 9번째 tr안에 첫번째 td안에 첫번째 img Tag를 가져오라는 뜻이다 -ㅅ-;;


만화그림 다운로드 받는데 쓰다가.
나중에 네이버나 다음에 있는 상장된 기업정보 페이지를 모두 긁어서 데이타베이스 구축하는데도 사용했다.


야후는 일정시간에 많은 GET이 날라가면 자동으로 해당 아이피 Block을 시켜버린다=ㅅ=
외국 기업이라 그런지 그런 기본적인 것에 충실하다.



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

Class Design  (0) 2009.03.07
나쁜 디자인의 징후  (0) 2009.02.22
Design Principle - SRP  (0) 2009.02.22
Method Design  (0) 2009.02.11
몇가지 프로그래밍 조언  (0) 2009.02.10
Posted by bleujin