2009-11-29

Ruby on rails에서 테이블없이 모델 사용하기

Ruby on rails에서 테이블에 매핑되지 않는 입력 폼을 검증할 필요가 있었다.

구글링에서 얻은 가장 만족스러운 방법은 다음이다.

Tableless models in RailsTableless models in Rails


플러그인이나 라이브러리도 필요 없고, 직접 칼럼을 선언하면 된다. 자바에서 get/set을 만드는 걸 생각하면 아무 것도 아니다.

class OnlineDemoRequest < ActiveRecord::Base
  def self.columns() @columns ||= []; end
 
  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end
 
  column :email, :string
  column :first_name, :string
  column :last_name, :string
  column :company, :string
 
  validates_presence_of :email
  validates_format_of :email, :with => /^$|^\S+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,4})(\]?)$/ix
  validates_presence_of :first_name
  validates_presence_of :last_name
  validates_presence_of :company

end


* 2.3.4에서 테스트를 했다.

Procedure와 Function

메소드는 Procedure와 Function으로 구분된다.

기계적이거나 배타적인 구분을 의미하지는 않는다.

Procedure는 상태의 변화(side effect)를 야기한다.

Function은 무언가를 반환한다.

2009-11-27

Option, Preference, Parameter, Attribute, Property

애플리케이션을 설정할 수 있어야 한다. 이를 Option, Preference, Parameter, Attribute, Property 등의 용어 중에서 무엇을 설명할까?


Parameter

웹 애플리케이션이라면 HTTP 파라미터와의 혼돈으로 사용하지 않는 것이 좋다.


Attribute

모델(혹은 객체)의 속성으로 사용하는 쪽이 더 좋다.


Property

자바라면 System.getProperty 메소드와의 혼돈으로 사용하지 않는 것이 좋다.


Preference 혹은 Option

둘 중에 무엇을 선택해야 할지는 모르겠다. Preference는 사용자와 관련된 느낌을 준다. 누군가와 대화를 할 때는 Option이 더 좋다. 사용자와 애플리케이션에 대해서 따로 설정한다면 둘 다 사용하면 된다. 그렇지만 둘을 굳이 분리해야 하는가?

구현을 java.util.prefs 패키지로 한다면 Preference를 사용하여야 겠지만...



Option/Preference는 컨텍스트에 따라서 달라저야 하는가?

예를 들어 파일 업로드 최대 크기를 보자.

- 전체 애플리케이션에 설정할 수 있다
- 모듈에 따라서 다르게 설정할 수 있다. 모듈은 계층적이다.
- 사용자 혹은 그룹에 따라서 다르게 설정할 수 있다. 그룹은 계층적일 수도 있다.

이렇게 하면 유연성을 얻지만 복잡해진다.

2009-11-26

AJAX를 사용할 수 없을 때

도메인이 다르면 AJAX를 사용할 수 없다. 자바 스크립트로 이를 흉내내는 방법이 있다.

var HTTP = {};

HTTP.getTextWithScript = function(url, callback) {
    var script = document.createElement("script");
    document.body.appendChild(script);
    var funcname = "func" + HTTP.getTextWithScript.counter++;
    HTTP.getTextWithScript[funcname] = function(text) {
                                           callback(text);
                                           document.body.removeChild(script);
                                           delete HTTP.getTextWithScript[funcname];
                                       }
    var emptyImage = new Image();
    emptyImage.onerror =
        function() {
            Progress.hide();
            callback("<div>Error Happened</div>");
        };
    emptyImage.onload =
        function() {
            script.src = HTTP.url + url + "&func=" + encodeURIComponent("HTTP.getTextWithScript." + funcname);
        };
    emptyImage.src = HTTP.url + "/empty.gif";
}

HTTP.getTextWithScript.counter = 0;

해당 서버에 empty.gif 이미지가 존재해야 한다. 서버가 응답하면 문제가 없지만 그렇지 않으면 행이 걸릴 수 있다. 이를 방지하기 위해서 이미지 파일를 이용한다.



사용 방법은 다음과 같다.

HTTP.url = url;
Progress.show("result");
HTTP.getTextWithScript("/some_request",
                       function(text) {
                           $("result").innerHTML = text;
                           Progress.hide();
                           DocResize.adjustHeight();
                       }
                       );



자바(서버 사이드)에서는 다음과 같이 처리한다.

JavascriptCallResponse javascriptResponse = new JavascriptCallResponse(response);
include(request, javascriptResponse, "actual_page.jsp");

response.setContentType("text/javascript; charset=UTF-8");
String func = request.getParameter("func");
PrintWriter writer = response.getWriter();
String text = StringUtils.strip(javascriptResponse.getContent().toString()).replaceAll("(?m)\\s+", " ");
writer.write(func + "('" + text + "');");

실제 화면 로직 처리는 actual_page.jsp에 있다.


JavascriptCallResponse 클래스는 다음과 같다.

package com.dimdol.example;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class JavascriptCallResponse extends HttpServletResponseWrapper {

    private PrintWriter writer;

    private StringWriter stringWriter;

    public ReportServletResponse(HttpServletResponse response) {
        super(response);
    }

    public String getContentType() {
        return "text/html; charset=UTF-8";
    }

    public PrintWriter getWriter() throws IOException {
        if (writer == null) {
            stringWriter = new StringWriter();
            writer = new PrintWriter(stringWriter);
        }
        return writer;
    }

    public StringBuffer getContent() {
        return stringWriter == null ? new StringBuffer() : stringWriter.getBuffer();
    }

}



* 보안 문제일까? 일부 웹 브라우저에서는 동작하지 않는다. 그래서 이 방법보다는 서버에서 프락시 방식으로 처리하는 걸 선호한다.


* JavaScript: The Definitive Guide에 있는 내용을 참조

Guard Clause

이 코드보다는

void initialize() {
  if (!isInitialized()) {
    ...
  }
}


이 코드가 좋다

void initialize() {
  if (isInitialized()) {
   return;
  }
  ...
}



이 코드를 보면 더 명확하다.

void compute {
  Server server = getServer();
  if (server != null) {
    Client client = server.getClient();
    if (client != null) {
      Request current = client.getRequest();
      if (current != null) {
        processRequest(current);
     }
  }
}


void compute {
  Server server = getServer();
  if (server == null)
    return;
  Client client = server.getClient();
  if (client == null)
    return;
  Request current = client.getRequest();
  if (current == null)
    return;
  processRequest(current);
}



* 켄트 백의 구현 패턴 7장 Behavior에서 발췌

2009-11-25

내가 사용하는 소프트웨어

내가 사용하는 소프트웨어들이다.

운영 체계는 윈도우 XP ㅠㅠ... 우분투 사용을 시도했지만 IE 테스트 때문에...

웹 브라우저는 파이어폭스...  주요 웹 브라우저들을 대부분 설치했지만 호환성 테스트가 아니면 사용하지는 않는다.

파이어폭스 플러그은 파이어버그만... 웹 개발자의 축복...


메일은 썬더버드(지메일에 붙어서)를 사용하는데... 지메일을 직접 사용하는 것도 괜찮을 듯...

메신저는 구글 톡만(친구가 별로 없다^^) 사용한다. 파일 전송을 하려면 영어판을 사용해야 한다.


백신은 회사에서 라이센스를 구매한 알약을 쓴다. ClamWin을 사용하기도 했는데...


동영상은 KMP를, PDF 문서는 Adobe Acrobat Reader로...

텍스트 편집 도구로는 Notepad++를...

간단한 그래픽 작업을 위해서 Paint.NET를... 닷넷을 설치해야 한다.

압축 프로그램으로는 빵집을...

텔넷/ssh 클라이언트로는 putty를...

CD를 구울 때는 InfraRecorder...

로그를 볼 때는 TailMe...



개발을 위해서 자바(JDK는 1.5와 1.6 모두, JRE는 1.6만)와 이클립스를...

플렉스 개발을 위해서 Flex SDK도...


기타 등등으로

- 클릭 투 트윅(알약에도 비슷한 기능이 있어서 지금은 사용하지 않는다.)
- 오라클 인스턴트 클라이언트
- Microsoft Script Debugger
- Linuxnew

패키지 소프트웨어 갖추어야 하는 것들

패키지 소프트웨어가 갖추어야 하는 것들을 나름대로 정리해봤다.

PC에 설치하는 개인용 소프트웨어가 아닌 서버에 설치하는 웹 기반 소프트웨어를 대상으로 한다.


자동 업데이트

Saas가 갖는 장점들이다.

What are the Advantages of Web-Based Software?

그리고 이는 패키지 소프트웨어의 단점이 된다. 모든 것을 극복할 수 없겠지만 PC 용 소프트웨어 처럼 자동 업데이트 기능만 있다면 만족할 수 있다.

패키지가 단순할 수록 자동 업데이트 구현이 쉽다.

이 때 데이터베이스 구조와 같은 내부적인 것들도 자동으로 변경해야 한다.


데이터 백업 및 복원

데이터가 날아가면 끝이다. 백업은 필수다. 백업을 하면 복원도 덩달아 필요하다.

손쉬운 건 일자별 백업이다. 일자별 백업을 하면 오늘 발생한 데이터는 어떻게? 결국 트랜잭션 로그를 남겨야 한다.

하드 디스크는 무조건 2개 이상이 필요하다.


이벤트 처리

무슨 일이 일어나는지 기록하면 좋다. 꼭 필요한 건 아니다.


스케줄러

주기적으로 데이터 백업을 수행하려면 스케줄러가 필요하다. Crontab 수준일 필요는 없다. 그러나 Crontab 만큼 별다른 고민없이 구현 가능한 것도 없다.

예외 처리

이 코드보다는

try {
   ...
} catch (Exception e) {
   // ignore exception
}


이 코드가

try {
    ...
} catch (Exception ignore) {}

좋다.

2009-11-24

패키지와 프레임워크

예전 회사에서는 프레임워크 개발을 했다. 스프링과 같은 자바 프레임워크였고, 이를 기반으로 많은 SI 프로젝트들이 진행되었다.

지금 회사에서는 패키지 솔루션 개발을 한다. 말로만 패키지지 실제로는 SI인 솔루션들도 많지만 설치 이후에 거의 수정을 하지 않는 진정한(?) 솔루션을 개발한다.


프레임워크 개발은 익명의 다수의 개발자를 대상으로 하기 때문에 제약이 많다. 학습과 경험에 의해서 이것 저것 고쳐야 할 것들이 보여도 수정하기가 어렵다. 이 문제를 해결하려고 잡다한 시도들을 했다.

컴포넌트 기반 개발을 축으로 무조건적으로 인터페이스와 구현을 분리하고, IoC 패턴으로 컴포넌트 간의 상관 관계를 쉽게 수정할 수 있도록 했다. 결과적으로 늘어나는건 적용된 디자인 패턴 숫자와 XML 파일... 그리고 이에 비례한 복잡도...

프레임워크 개발을 할 때는 중요하지 않은 문제를 중요하다고 간주하며 살던 시기였다.


솔루션을 개발하면서는 가능한 쉽게 가는 쪽을 선택했다. 골격은 MVC 패턴을 준수했지만 설정 부분을 다 하드코딩으로 처리햇다. XML 파일을 수정하나 코드를 수정하나 차이도 없고, 컴파일러가 오류를 잡아 주기 때문에  오타 가능성이 현격하게 낮아진다는 장점도 있다.

HiveMind와 같은 IoC 컨테이너도 쓰레기 통으로 던졌다. 필요한 객체를 직접 생성해서 쓰면 되지 굳이 set 메소드를 만들고, 이를 XML 파일에 설정해야 할 이유를 찾을 수 없었다.


물론 솔루션 개발에서 이런 태도로 전환할 수 있었던 것은 내가 작성한 코드를 다른 것에 영향을 주거나 받지 않고 쉽게 수정할 수 있기 때문이다. 현재의 나와 지금 함께 개발하고 있는 개발자들, 그리고 미래의 나와 미래에 나와 함께할 개발자들 혹은 내가 떠난 후 내 일을 인수 인계받을 개발자들만을 염두하면 된다.

결론적으로 어떤 코드든 쉽게 수정할 수 있다는 것이 솔루션 개발의 장점이다.

물론 패키지 솔루션에서도 데이터베이스와 파일 구조를 변경하거나 외부 개발자들에게 공개한 오픈 API를 변경하는 것은 곤란한 일이다.

 
요약하면 유연성에 대한 태도도 바뀌었다. 전에는 고도의 기법을 통한 확장 가능성을 유연성으로 이해했다면 지금은 쉽게 이해할 수 있도록 간결하게 작성하는 것을 유연성을 확보하는 통로로 받아들이고 있다. 쉽게 이해할 수 있도록 간결하다면 수정하는 건 어렵지 않다.



2009-11-22

Ruby on rails에서 Gmail을 이용해서 메일 보내기

Ruby on rails에 기반한 애플리케이션에서 Gmail SMTP를 이용해서 메일을 보내야 했다.

구글링 결과 플러그인을 사용하면 된단다. 시도 때도 없이 플러그인(혹은 라이브러리)을 쓰는 건 피하고 싶기에 머뭇거렸다.

그런데 댓글들을 읽다가 플러그인 없이도 가능하다는 정보에 반색하며 시도해봤다.

우선, config/environment.rb 파일에 다음 내용을 추가한다.

ActionMailer::Base.smtp_settings = {
  :enable_starttls_auto => true,
  :address => "smtp.gmail.com",
  :port => 587,
  :domain => "mycompany.com",
  :authentication => :plain,
  :user_name => "username@mycompany.com",
  :password => "password"
}

enable_starttls_auto를 true로 설정해야 한다는 것을 제외하면 일반 설정과 다르지 않다.

그런데 이렇게 해도 메일이 발송되지 않았다. 에러 메시지는 없고 메일이 정상적으로 발송된 것 처럼 로그가 기록된다.


댓글들을 자세하게 읽어본 결과 루비 1.8.7 이상을 사용해야 한다고 한다. 내가 사용하는 윈도우 개발 환경은 1.8.6이었다.

루비 윈도우 인스톨 버전이 1.8.6이다.


루비 1.8.7이 설치되어 있는 리눅스 운영 환경에는 정상적으로 메일이 전송되는 것을 확인했다.


* Rails 버전은 2.3.2이다.

소프트웨어 개발이 추구해야 하는 방향

소프트웨어 개발 조직은 개발 비용을 절감해야 한다. 이는 개발 생산성을 높이는 것의 다른 이름이다. 그렇다면 다양한 요소로 이루어진 비용에서 어떤 비용을 줄이는데 초점을 맞추어야 하는가?

소프트웨어 개발 비용(total)은 이렇게 구성된다.
cost
total = costdevelop + costmaintain

일반적인 견해대로

costdevelop < costmaintain

이라면 유지보수(maintain) 비용 절감이 우순 순위를 갖는다.


유지보수 비용 구성은 아래와 같다.

costmaintain = costunderstand + costchange + costtest + costdeploy


켄트 백은 이중에서 기존 코드를 이해(understand)하는데 소요되는 비용이 가장 큰 비중을 차지한다고 주장한다.

그 주장에 동의한다면 소프트웨어 개발 비용(total)을 최소화하려면 코드 이해에 소요되는 비용을 줄어야 한다는 결론에 도다른다.



초기 개발 단계에서 유지 보수 비용을 회피하거나 최소화하는 방법으로 고도의(혹은 복잡한) 개발 기법을 이용해서 확장 가능한 소스를 개발하는 구현 전략은 유효할까?

이 시도가 갖는 첫번째 문제점은 시도 자체가 미완성으로 끝날 가능성이 높다는 점이다. 미래는 예측하기 어렵다. 불확실성을 제어할 방법이 마땅치 않다.

두번째로 일어날 일을 예측하기 어려운 점도 있지만, 예측한 일이 일어나지 않는다는 점도 문제점이다. 확장을 위해서 개발해 놓은 것들이 사용되지 않는다면 그 비용을 회수할 방법이 없다.

세번째로 돈의 시간 가치를 생각했을 때 비용 발생 시점을 현재보다는 미래로 연기해야 한다는 재무적인 측면에서의 문제점도 있다.




* 이 글은 켄트 벡이 쓴 구현 패턴(Implementation Patterns)을 인용하고 해석한 것이다. 그 중에서도 4장 Motivation을 다룬다.

2009-11-09

자바 2D와 CPU 사용률

자바 2D로 액티브한 프로그램을 작성한 경우에 CPU 사용률을 몇 가지 옵션으로 최적화할 수 있다.

특히 하드웨어 가속 기능을 사용하는 경우에 역설적으로 더 느려지는 현상도 관찰된다. 하드웨어 가속을 OFF할 수 없는 환경에서 다음 옵션으로 자바만을 제어할 수 있다.

-Dsun.java2d.d3d=false

이렇게 설정하면 Direct3D를 사용하지 않게 되면서 하드웨어 가속 기능을 OFF한 것과 동일한 CPU 사용률을 보여준다.

* Direct3D 드라이버를 업데이트하면 하드웨어 가속기를 사용하면서도 성능 향상을 얻을 수도 있다.


또한 다음과 같이 설정하면 (Swing Back Buffer에 대한 DirectDraw와 Direct3D를 사용하지 않도록 설정)

-Dsun.java2d.ddoffscreen=false

CPU 사용률을 더 줄일 수 있다.


다음 옵션은 DirectDraw와 Direct3D를 아예 사용하지 않도록 하는 것인데 CPU 사용률 측면에서는 이전과 큰 차이가 없다.

-Dsun.java2d.noddraw=true (false가 아닌 true)


코딩 방법과 하드웨어에 따라서 상이한 결과가 나타날 수 있다.



[참고 자료]

http://java.sun.com/javase/6/docs/technotes/guides/2d/flags.htm