JavaScript - 개체 지향 기술을 이용한 고급 웹 응용 프로그램 만들기

|
JavaScript
개체 지향 기술을 이용한 고급 웹 응용 프로그램 만들기
Ray Djajadinata

이 기사에서 다루는 내용:
  • 프로토타입 기반 언어로서의 JavaScript
  • JavaScript를 사용한 개체 지향 프로그래밍
  • JavaScript에서의 코딩 트릭
  • JavaScript의 미래
이 기사에서 사용하는 기술: 
JavaScript
최근에 필자는 웹 응용 프로그래밍 개발 부문에 5년 경력을 가진 소프트웨어 개발자와 인터뷰를 했습니다. 그녀는 4년 6개월 동안 JavaScript를 사용했으며 스스로의 JavaScript 숙련도를 매우 높게 평가하고 있었습니다. 나중에 알게 되었지만 이 개발자는 JavaScript에 대해서 그리 많은 것을 알고 있지는 못했습니다. 이 개발자를 탓하고자 하는 것은 아닙니다. JavaScript에는 이렇듯 상당히 재미있는 면이 있습니다. 많은 개발자들이 C/C++/C#에 대해 알고 있거나 프로그래밍 경험이 있다는 이유만으로 자신이 JavaScript에 대해 많은 것을 알고 있다고 착각하곤 합니다. 필자 역시 최근까지 그러했습니다.
이러한 가정이 전혀 근거가 없는 것은 아닙니다. JavaScript를 사용하여 간단한 작업을 수행하기는 상당히 쉬우며 시작하기도 어렵지 않습니다. 이 언어는 또한 까다롭지 않으며 코딩을 시작하기 전에 많은 것을 배우도록 요구하지도 않습니다. 프로그래머가 아니더라도 몇 시간만 작업하면 홈페이지를 위한 유용한 스크립트를 작성할 수 있을 정도입니다.
사실 최근까지도 필자는 JavaScript에 대한 짧은 지식과 MSDN® DHTML 참조, 그리고 C++/C# 경험을 활용하여 필요한 작업을 수행할 수 있었습니다. 필자의 JavaScript 실력이 부족하다는 것을 느끼게 된 것은 현실적인 AJAX 응용 프로그램을 작성하기 시작하면서부터였습니다. 이 신세대 웹 응용 프로그램의 복잡성과 상호 작용성은 JavaScript 코드를 작성하는 데 있어 완전히 다른 방식을 요구하고 있습니다. 이것은 본격적인 JavaScript 응용 프로그램이며 지금까지 스크립트를 작성하던 느슨한 방법으로는 이러한 응용 프로그램을 작성할 수 없습니다.
개체 지향 프로그래밍(OOP)은 여러 JavaScript 라이브러리에서 코드베이스를 더욱 유지 관리하기 쉽게 하기 위해 널리 사용되는 방법입니다. JavaScript는 OOP를 지원하지만 널리 사용되는 Microsoft® .NET Framework 지원 언어인 C++, C# 또는 Visual Basic®에서 지원하는 방법과는 상당히 다른 방법으로 제공합니다. 따라서 이러한 언어를 오랫동안 사용한 개발자라면 JavaScript에서 OOP를 사용하는 것이 낯설고 처음에는 사용하기 까다롭게 느껴질 수 있습니다. 이 기사에서는 JavaScript 언어에서 어떻게 개체 지향 프로그래밍을 지원하는지 자세히 살펴보고 이러한 지원을 사용하여 JavaScript에서 효과적으로 개체 지향 개발을 수행하는 방법을 알아보겠습니다. 우선 무엇보다 중요한 개체에 대해서 살펴보겠습니다.

JavaScript 개체는 사전입니다.
C++나 C#에서의 개체는 클래스나 구조체의 인스턴스를 의미합니다. 개체는 어떤 템플릿(클래스)에서 인스턴스화했는지에 따라 다른 속성 및 메서드를 가지지만 JavaScript 개체는 이와 다릅니다. JavaScript의 개체는 단순히 이름/값 쌍의 컬렉션입니다. JavaScript 개체는 문자열 키가 있는 사전으로 생각할 수 있습니다. 익숙한 "."(점) 연산자나 일반적으로 사전을 처리할 때 사용되는 "[]" 연산자를 사용하여 개체 속성을 얻거나 설정할 수 있습니다. 다음 코드 조각을 살펴보십시오.
var userObject = new Object();
userObject.lastLoginTime = new Date();
alert(userObject.lastLoginTime);    
위 코드는 다음 코드와 정확히 같은 작업을 수행합니다.
var userObject = {}; // equivalent to new Object()
userObject[“lastLoginTime”] = new Date();
alert(userObject[“lastLoginTime”]);
다음과 같이 userObject 정의 내에서 직접 lastLoginTime 속성을 정의할 수도 있습니다.
var userObject = { “lastLoginTime”: new Date() };
alert(userObject.lastLoginTime);
C# 3.0 개체 이니셜라이저와 비슷하다는 것을 알 수 있을 것입니다. 또한 Python에 익숙한 개발자라면 두 번째와 세 번째 코드 조각에서 userObject를 인스턴스화하는 방식이 Python에서 사전을 지정하는 방식과 동일하다는 것도 알 수 있습니다. 유일한 차이는 JavaScript 개체/사전은 문자열 키만 받지만 Python 사전에는 hashable과 같은 개체를 사용할 수 있다는 것입니다.
이러한 예는 JavaScript 개체가 C++ 또는 C# 개체에 비해 얼마나 유연한가를 보여 줍니다. lastLoginTime을 앞서 선언할 필요는 없으며 userObject에 해당하는 이름의 속성이 없는 경우에는 간단하게 userObject에 속성이 추가됩니다. JavaScript 개체가 사전이라는 사실을 기억한다면 이것은 그리 놀라운 일이 아닙니다. 사전에는 항상 새로운 키와 해당하는 값을 추가할 수 있습니다.
개체 속성은 앞서 설명한 것과 같습니다. 그렇다면 개체 메서드는 어떨까요? 이번에도 역시 JavaScript는 C++/C#과는 다릅니다. 개체 메서드를 이해하기 위해서는 먼저 JavaScript 함수를 살펴볼 필요가 있습니다.

JavaScript에서는 함수가 가장 중요합니다.
함수와 개체를 서로 다른 것으로 취급하는 프로그래밍 언어가 많습니다. JavaScript에서는 이 차이가 모호합니다. JavaScript 함수는 실행 가능한 코드와 연결된 개체입니다. 다음과 같은 일반적인 함수를 예로 들어 보겠습니다.
function func(x) {
    alert(x);
}
func(“blah”);
이 코드는 JavaScript에서 일반적으로 함수를 정의하는 방법을 보여 줍니다. 그러나 다음과 같이 익명의 함수 개체를 만들고 변수 func에 할당하는 방법으로도 동일한 함수를 정의할 수 있습니다.
var func = function(x) {
    alert(x);
};
func(“blah2”);
또는 다음과 같이 Function 생성자를 사용할 수도 있습니다.
var func = new Function(“x”, “alert(x);”);
func(“blah3”);
이를 통해서 함수는 함수 호출 작업을 지원하는 개체라는 것을 알 수 있습니다. Function 생성자를 사용하여 함수를 정의하는 마지막 방법은 자주 사용되지는 않지만 흥미로운 가능성을 열어 줍니다. 그 이유는 함수 본문이 Function 생성자에 대한 String 매개 변수이기 때문입니다. 이것은 런타임에 임의의 함수를 작성할 수 있음을 의미합니다.
함수가 개체라는 것을 확인시켜 주는 예로서 다른 JavaScript 개체를 대상으로 작업할 때와 마찬가지로 함수에 대해 속성을 설정하거나 추가할 수 있습니다.
function sayHi(x) {
    alert(“Hi, “ + x + “!”);
}
sayHi.text = “Hello World!”;
sayHi[“text2”] = “Hello World... again.”;

alert(sayHi[“text”]); // displays “Hello World!”
alert(sayHi.text2); // displays “Hello World... again.”
함수는 개체이므로 변수에 할당하고 다른 함수에 인수로 전달하며 다른 함수에서 값으로 반환하는 것은 물론, 개체의 속성이나 배열의 요소로 저장하는 등의 작업이 가능합니다. 그림 1에는 이에 대한 예가 나와 있습니다.
이를 기억한다면 이름을 선택하고 이 이름에 함수를 할당하는 작업으로 간단하게 개체에 메서드를 추가할 수 있습니다. 여기에서는 익명 함수를 해당 메서드 이름에 할당하여 개체에 3개의 메서드를 정의했습니다.
var myDog = {
    “name” : “Spot”,
    “bark” : function() { alert(“Woof!”); },
    “displayFullName” : function() {
        alert(this.name + “ The Alpha Dog”);
    },
    “chaseMrPostman” : function() { 
        // implementation beyond the scope of this article 
    }    
};
myDog.displayFullName(); 
myDog.bark(); // Woof!
C++/C# 개발자에게는 displayFullName 함수 내에 사용된 "this" 키워드가 친근하게 느껴질 것입니다. 이 키워드는 메서드를 호출한 개체를 참조합니다. Visual Basic 개발자에게도 역시 생소하지는 않을 것입니다. Visual Basic에서는 이를 "Me"라고 합니다. 따라서 위 예에서 displayFullName의 "this" 값은 myDog 개체입니다. 그러나 "this"의 값은 정적이지 않습니다. 그림 2에서 보여 주고 있는 것처럼 다른 개체를 통해 호출된 경우에는 "this" 값이 해당 개체를 가리키도록 변경됩니다.
그림 2의 마지막 줄에서는 개체의 메서드로 함수를 호출하는 대안을 보여 줍니다. JavaScript에서 함수는 개체라는 사실을 기억하십시오. 모든 함수 개체에는 함수를 첫 번째 인수의 메서드로 호출하는 메서드 이름 호출이 있습니다. 즉, 호출에 전달하는 개체의 첫 번째 인수가 이 함수 호출에서 "this" 값이 됩니다. 뒤에 살펴보겠지만 이것은 기본 클래스 생성자를 호출하는 유용한 기술입니다.
한 가지 기억해야 할 것은 개체를 소유하지 않은 상태에서 "this"가 포함된 함수를 호출해서는 안 된다는 것입니다. 이러한 호출에서 "this"는 Global 개체를 참조하므로 전역 네임스페이스를 어지럽히게 되고 이것은 응용 프로그램에 심각한 재앙이 될 수 있습니다. 예를 들어 아래의 스크립트는 JavaScript의 전역 함수 isNaN의 동작을 변경하며 이러한 일은 절대 피해야 합니다.
alert(“NaN is NaN: “ + isNaN(NaN));

function x() {
    this.isNaN = function() { 
        return “not anymore!”;
    };
}
// alert!!! trampling the Global object!!!
x();

alert(“NaN is NaN: “ + isNaN(NaN));
지금까지 개체를 만들고 속성과 메서드로 완성하는 방법을 살펴보았습니다. 그러나 위의 코드 조각을 자세히 살펴보면 속성과 메서드가 개체 정의 자체 내에 코드로 포함되어 있다는 것을 알 수 있습니다. 개체 생성에 대한 더 본격적인 제어가 필요한 경우에는 어떻게 해야 할까요? 예를 들어 몇 가지 매개 변수의 값에 따라 개체 속성의 값을 계산해야 할 수 있습니다. 또는 런타임에만 얻을 수 있는 값으로 개체 속성을 초기화해야 할 수 있습니다. 이외에도 개체의 인스턴스를 두 개 이상 만들어야 할 수 있는데 이것은 매우 일반적인 요구 사항입니다.
C#에서는 개체 인스턴스를 인스턴스화하는 데 클래스를 사용하지만 JavaScript에는 클래스가 없으므로 상황이 다릅니다. 다음 섹션에서 살펴보겠지만 JavaScript에서는 "new" 연산자와 함께 사용하는 경우 함수가 생성자처럼 동작한다는 사실을 활용하게 됩니다.

생성자 함수는 있지만 클래스는 없습니다.
JavaScript OOP의 특이한 점은 앞서 언급했듯이 JavaScript에는 C# 또는 C++와 같은 클래스가 없다는 것입니다. C#에서의 작업 방법은 다음과 같습니다.
Dog spot = new Dog();
위 코드를 수행하면 클래스 Dog의 인스턴스인 개체를 얻을 수 있습니다. 그러나 JavaScript에서는 클래스가 없습니다. 클래스와 가장 비슷한 효과를 얻는 방법은 다음과 같이 생성자 함수를 정의하는 것입니다.
function DogConstructor(name) {
    this.name = name;
    this.respondTo = function(name) {
        if(this.name == name) {
            alert(“Woof”);        
        }
    };
}

var spot = new DogConstructor(“Spot”);
spot.respondTo(“Rover”); // nope
spot.respondTo(“Spot”); // yeah!
여기에서는 무슨 일이 일어나고 있을까요? DogConstructor 함수 정의에 대해서는 잠시 무시하고 다음 줄을 살펴보십시오.
var spot = new DogConstructor(“Spot”);
"new" 연산자가 수행하는 일은 간단합니다. 우선 이 연산자는 비어 있는 새 개체를 만듭니다. 그런 다음 비어 있는 새 개체를 함수 내의 "this" 값으로 설정하고 이어지는 함수를 호출합니다. 다른 말로 하면 위의 코드에서 "new" 연산자는 아래의 두 줄과 비슷하다고 생각할 수 있습니다.
// create an empty object
var spot = {}; 
// call the function as a method of the empty object
DogConstructor.call(spot, “Spot”);
DogConstructor의 본문에서 볼 수 있는 것처럼 이 함수를 호출하면 해당 호출 시 키워드 "this"가 참조하는 대상으로 개체를 초기화합니다. 이러한 방법으로 개체의 템플릿을 만드는 것이 가능합니다. 비슷한 개체를 만들어야 할 때마다 생성자 함수로 "new"를 호출하면 완전하게 초기화된 개체를 결과로 얻을 수 있습니다. 클래스와 비슷하게 느껴지지 않습니까? 실제로 JavaScript에서 일반적으로 생성자 함수의 이름은 시뮬레이트하는 클래스의 이름이며 따라서 위 예에서는 생성자 함수 이름을 Dog로 지정할 수 있습니다.
// Think of this as class Dog
function Dog(name) {
    // instance variable 
    this.name = name;
    // instance method? Hmmm...
    this.respondTo = function(name) {
        if(this.name == name) {
            alert(“Woof”);        
        }
    };
}

var spot = new Dog(“Spot”);
위의 Dog 정의에서 필자는 name이라는 인스턴스 변수를 정의했습니다. Dog를 생성자 함수로 사용하여 생성한 모든 개체는 자체 인스턴스 변수 이름의 복사본을 가지게 됩니다. 이것은 앞서 언급했듯이 개체 사전에 대한 항목입니다. 예상할 수 있듯이 결국 모든 개체는 자체 상태를 저장하기 위한 자체 인스턴스 변수 복사본이 필요합니다. 그러나 다음 줄을 보면 Dog의 모든 인스턴스가 respondTo 메서드의 자체 복사본을 가지고 있음을 알 수 있습니다. 이것은 낭비이며 respondTo의 한 인스턴스를 Dog 인스턴스 간에 공유하면 충분합니다. 다음과 같이 respondTo 정의를 Dog 외부에 배치함으로써 이 문제를 해결할 수 있습니다.
function respondTo() {
    // respondTo definition
}

function Dog(name) {
    this.name = name;
    // attached this function as a method of the object
    this.respondTo = respondTo;
}
이 방법을 사용하면 Dog의 모든 인스턴스(즉, 생성자 함수 Dog를 사용하여 만든 모든 인스턴스)가 respondTo의 한 인스턴스를 공유할 수 있습니다. 그러나 메서드의 멤버가 늘어나면 이 방법은 점차 관리하기 어렵게 됩니다. 코드베이스 내에 많은 전역 함수가 생기게 되며 "클래스"의 수가 많아질수록, 해당 메서드의 이름이 비슷할수록 상황은 더욱 악화됩니다. 프로토타입 개체를 사용하는 더 좋은 방법이 있으며 자세한 내용은 다음 섹션에서 살펴보겠습니다.

프로토타입
프로토타입 개체는 JavaScript의 개체 지향 프로그램에서 핵심적인 개념입니다. 프로토타입이라는 이름은 JavaScript에서 기존 예(말하자면 프로토타입) 개체의 복사본에서 개체를 만들기 때문에 붙여진 것입니다. 이 프로토타입 개체의 모든 속성 및 메서드는 이 프로토타입의 생성자로 만드는 모든 개체의 속성 및 메서드로 나타나게 됩니다. 이러한 개체가 해당 프로토타입에서 속성 및 메서드를 상속받는다고 할 수 있습니다. 다음과 같이 새 Dog 개체를 만든다고 가정해 보겠습니다.
var buddy = new Dog(“Buddy“);
프로토타입을 가져온 단 한 라인에서는 명백하게 보이지 않을 수 있지만 buddy에서 참조한 개체는 해당 프로토타입에서 속성 및 메서드를 상속하게 됩니다. 개체 buddy의 프로토타입은 생성자 함수(이 경우에는 Dog 함수)의 속성에서 가져옵니다.
JavaScript의 모든 함수에는 프로토타입 개체를 참조하는 "prototype"이라는 속성이 있습니다. 이 프로토타입 개체에는 함수 자체를 참조하는 "constructor"라는 이름의 속성이 있습니다. 이것은 일종의 순환 참조라고 할 수 있는데 그림 3에서는 이러한 순환 관계를 더 잘 보여 주고 있습니다.
그림 3 모든 함수 프로토타입에는 생성자 속성이 있음 
이제 "new" 연산자로 개체를 만들기 위해 함수(이 예에서는 Dog)를 사용하면 결과 개체는 Dog.prototype의 속성을 상속하게 됩니다. 그림 3을 보면 Dog.prototype 개체에 다시 Dog 함수를 가리키는 생성자 속성이 있음을 알 수 있습니다. 따라서 모든 Dog 개체(Dog.prototype에서 상속한) 또한 Dog 함수를 가리키는 생성자 속성을 가지는 것처럼 보입니다. 그림 4의 코드에서 이를 확인할 수 있습니다. 생성자 함수, 프로토타입 개체 및 이들 사용하여 만든 개체 간의 관계는 그림 5에 표시되어 있습니다.
그림 5 해당 프로토타입에서 상속된 인스턴스 
그림 4에서 hasOwnProperty 및 isPrototypeOf 메서드에 대한 호출을 볼 수 있을 것입니다. 이러한 메서드는 어디에서 온 것일까요? Dog.prototype에서 온 것은 아닙니다. 실제로 Dog.prototype 및 Dog의 인스턴스에서 호출할 수 있는 toString, toLocaleString 및 valueOf와 같은 다른 메서드도 있으며 이들 역시 Dog.prototype에서 온 것은 아닙니다. .NET Framework에도 모든 클래스에 대한 궁극적인 기본 클래스 역할을 하는 System.Object가 있는 것처럼 JavaScript에도 모든 프로토타입에 대한 궁극적인 기본 프로토타입인 Object.prototype이 있습니다. Object.prototype의 프로토타입은 Null입니다.
이 예에서는 Dog.prototype이 개체라는 것을 기억하십시오. 보이지는 않지만 이 개체는 Object 생성자 함수를 호출함으로써 생성되었습니다.
Dog.prototype = new Object();
따라서 Dog의 인스턴스가 Dog.prototype에서 상속하는 것처럼 Dog.prototype은 Object.prototype에서 상속합니다. 결국 Dog의 모든 인스턴스는 Object.prototype의 메서드 및 속성 또한 상속하게 됩니다.
모든 JavaScript 개체는 Object.prototype으로 끝나는 프로토타입의 체인을 따라 상속합니다. 지금까지 살펴본 이 상속은 라이브 개체 간의 상속입니다. 선언되는 시점에 클래스 간에 일어나는 상속의 일반적인 개념과는 차이가 있습니다. 결과적으로 JavaScript 상속은 훨씬 동적이며 다음과 같은 간단한 알고리즘을 사용합니다. 개체의 속성/메서드에 액세스하려고 시도하면 JavaScript는 해당 속성/메서드가 개체에 정의되어 있는지 확인합니다. 개체에 정의되어 있지 않은 경우에는 개체의 프로토타입을 확인합니다. 여기에도 정의되어 있지 않으면 Object.prototype에 이를 때까지 개체 프로토타입의 프로토타입을 계속 확인합니다. 그림 6에서는 이러한 확인 프로세스를 보여 줍니다.
그림 6 프로토타입 체인에서 toString() 메서드 확인 (더 크게 보려면 이미지를 클릭하십시오.)
JavaScript가 속성 액세스와 메서드 호출을 동적으로 확인함에 따르는 몇 가지 효과가 있습니다.
  • 프로토타입 개체에 대한 변경은 개체가 생성된 이후에도 개체에 영향을 줍니다.
  • 개체에 속성/메서드 X를 정의하여 동일한 이름의 속성/메서드는 해당 개체의 프로토타입에서 숨겨집니다. 예를 들어 Dog.prototype에서 toString 메서드를 정의하여 Object.prototype의 toString 메서드를 다시 정의할 수 있습니다.
  • 변경은 프로토타입에서 해당 파생 개체로 한 방향으로만 진행하며 반대로는 진행하지 않습니다.
그림 7에서는 이러한 결과를 보여 줍니다. 그럼 7에서는 또한 앞서 발생했었던 불필요한 메서드 인스턴스 문제를 해결하는 방법도 보여 줍니다. 모든 개체가 별도의 함수 개체를 가지도록 하는 것이 아니라 메서드를 프로토타입 내에 배치함으로써 개체가 메서드를 공유하도록 할 수 있습니다. 이 예에서 getBreed 메서드는 spot에서 toString 메서드를 다시 정의하기 전까지 rover 및 spot에 의해 공유됩니다. 이후에 spot은 자신의 getBreed 메서드 버전을 가지지만 rover 개체 및 새 GreatDane으로 만든 이후 개체는 GreatDane.prototype 개체에 정의된 getBreed 메서드의 인스턴스를 공유하게 됩니다.

정적 속성 및 메서드
인스턴스가 아닌 클래스에 연결된 속성이나 메서드, 말하자면 정적 속성 및 메서드가 필요가 경우가 있습니다. JavaScript에서 함수는 원하는 대로 속성과 메서드를 설정할 수 있는 개체이므로 이러한 경우에 대처하기가 수월합니다. JavaScript에서는 생성자 함수가 클래스를 대신하므로 다음과 같이 생성자 함수에 정적 메서드와 속성을 설정함으로써 클래스에 추가하는 효과를 얻을 수 있습니다.
    function DateTime() { }

    // set static method now()
    DateTime.now = function() {
        return new Date();
    };

    alert(DateTime.now());
JavaScript에서 정적 메서드를 호출하기 위한 구문은 C#에서의 구문과 거의 동일합니다. 생성자 함수의 이름은 사실상 클래스의 이름이므로 이는 놀라운 일이 아닙니다. 이제 클래스와 공용 속성/메서드, 그리고 정적 속성/메서드가 있습니다. 이외에 어떤 것이 필요할까요? 우선 전용 멤버가 있습니다. 그러나 JavaScript에는 전용 멤버에 대한 기본 지원이 없습니다. 적어도 보호되는 것은 없으며 모두가 개체의 모든 속성과 메서드에 액세스할 수 있습니다. 클래스에 전용 멤버를 추가하는 방법이 있지만 이를 위해서는 먼저 차단에 대해서 이해해야 합니다.

차단
필자는 스스로 원해서 JavaScript를 배운 것은 아니었으며 JavaScript를 사용하지 않고 현실적인 AJAX 응용 프로그램을 개발한다는 것이 불가능하다는 것을 깨달은 후에야 배우기 시작했습니다. 처음에는 필자의 프로그래머 수준이 몇 단계가 떨어지는 것이 아닌가 하는 생각도 들었습니다. (JavaScript라니! C++를 사용하는 친구들이 뭐라고 말할까?) 그러나 처음 가지고 있던 거부감을 떨쳐내자 JavaScript가 상당히 강력하고 인상적이며 콤팩트한 언어라는 사실을 깨달았습니다. 필자는 또한 널리 사용되는 다른 언어에서 이제 막 지원하기 시작한 기능에 대해서 자랑하기도 했습니다.
JavaScript의 고급 기능 중 하나로 C# 2.0에서는 익명 메서드를 통해 지원되는 차단에 대한 지원이 있습니다. 차단은 안쪽 함수(C#에서는 안쪽 익명 메서드)가 바깥쪽 함수의 로컬 변수와 바인딩되었을 때 일어나는 런타임 현상입니다. 분명한 것은 이 안쪽 함수가 바깥쪽 함수 외부에서 액세스 가능하지 않다면 이것이 의미가 없게 된다는 것입니다. 한 가지 예를 살펴보겠습니다.
예를 들어 100보다 큰 수만 통과할 수 있고 나머지는 필터링되는 간단한 조건으로 일련의 수를 필터링해야 한다고 가정해 보겠습니다. 그림 8와 같이 함수를 작성할 수 있습니다.
그런데 이제는 다른 필터링 조건, 예를 들어 300보다 큰 수만 통과하도록 다른 필터링 조건을 만들어야 한다고 가정해 보겠습니다. 이제 다음과 같은 코드를 사용할 수 있습니다.
var greaterThan300 = filter(
    function(x) { return (x > 300) ? true : false; }, 
    someRandomNumbers);
그리고 50, 25, 10, 600 등의 수보다 큰 수를 필터링하게 할 수 있습니다. 영리한 독자라면 이 필터가 모두 "보다 큰"이라는 동일한 조건자를 사용하고 있으며 수만 다르다는 것을 간파할 수 있을 것입니다. 따라서 함수에서 수를 인수로 만들어 다음과 같이 작성할 수 있습니다.
function makeGreaterThanPredicate(lowerBound) {
    return function(numberToCheck) {
        return (numberToCheck > lowerBound) ? true : false;
    };
}
이제 다음과 같이 작업할 수 있습니다.
var greaterThan10 = makeGreaterThanPredicate(10);
var greaterThan100 = makeGreaterThanPredicate(100);
alert(filter(greaterThan10, someRandomNumbers));
alert(filter(greaterThan100, someRandomNumbers));
makeGreaterThanPredicate 함수에서 반환된 안쪽 익명 함수를 눈여겨보십시오. 이 익명 안쪽 함수는 makeGreaterThanPredicate로 전달된 인수인 lowerBound를 사용합니다. 일반적인 범위 지정의 규칙에 따르면 makeGreaterThanPredicate가 존재할 때는 lowerBound가 범위를 벗어나게 됩니다. 그러나 이 경우에 안쪽 익명 함수는 makeGreaterThanPredicate가 존재하는 한참 후에도 lowerBound를 가지고 있습니다. 이를 차단이라고 하는 것은 안쪽 함수가 정의된 위치에서 환경에 대해(바깥쪽 함수의 인수 및 지역 변수) 차단을 수행하기 때문입니다.
차단은 처음에는 사소한 기능처럼 보일 수도 있습니다. 그러나 제대로만 활용한다면 개발자의 아이디어를 코드로 구현하는 새롭고 흥미로운 가능성을 열어 줄 수 있습니다. JavaScript에서 가장 흥미로운 차단의 활용 예는 클래스의 전용 변수를 시뮬레이트하는 것입니다.

전용 속성 시뮬레이션
차단을 사용하여 전용 멤버를 시뮬레이트하는 방법을 살펴보겠습니다. 함수의 지역 변수는 일반적으로 함수 외부에서는 액세스할 수 없습니다. 함수가 더 이상 존재하지 않게 되면 지역 변수의 모든 실질적인 용도는 완전히 사라집니다. 그러나 지역 변수가 안쪽 함수의 차단에 의해 캡처되면 계속 존재하게 됩니다. 이것이 바로 JavaScript 전용 속성을 시뮬레이션하기 위한 핵심입니다. 다음 Person 클래스를 살펴보십시오.
function Person(name, age) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
    this.getAge = function() { return age; };
    this.setAge = function(newAge) { age = newAge; };
}
인수 name과 age는 생성자 함수 Person에 대해 로컬입니다. Person이 반환하는 순간 name과 age는 완전히 사라지게 됩니다. 그러나 여기에서 name과 age는 Person 인스턴스의 메서드로 할당된 네 함수에서 캡처되어 계속 유지되며 이러한 네 메서드 내에서만 엄격하게 액세스할 수 있게 됩니다. 즉, 다음과 같은 코드가 가능합니다.
var ray = new Person(“Ray”, 31);
alert(ray.getName());
alert(ray.getAge());
ray.setName(“Younger Ray”);
// Instant rejuvenation!
ray.setAge(22);
alert(ray.getName() + “ is now “ + ray.getAge() + 
      “ years old.”);
생성자에서 초기화되지 않은 전용 멤버는 다음과 같은 생성자 함수의 지역 변수가 될 수 있습니다.
function Person(name, age) {
    var occupation;
    this.getOccupation = function() { return occupation; };
    this.setOccupation = function(newOcc) { occupation = 
                         newOcc; };
  
    // accessors for name and age    
}
이러한 전용 멤버와 C#에서 사용하던 전용 멤버 사이에는 미세한 차이점이 있습니다. C#에서 클래스의 공용 메서드는 클래스의 전용 멤버를 액세스할 수 있었습니다. 그러나 JavaScript에서는 자체 차단 내에 이러한 전용 멤버를 가지고 있는 메서드를 통해서만 이러한 전용 멤버를 액세스할 수 있습니다. 이러한 메서드는 일반적인 공용 메서드와는 다르므로 보통 권한 있는 메서드라고 불립니다. 따라서 Person 공용 메서드 내에서도 전용 메서드에 접근하려면 권한 있는 접근자 메서드를 통해야 합니다.
Person.prototype.somePublicMethod = function() {
    // doesn’t work!
    // alert(this.name);
    // this one below works
    alert(this.getName());
};
Douglas Crockford는 차단을 사용하여 전용 멤버를 시뮬레이트하는 기술을 발견(또는 발표)한 첫 번째 인물로 널리 알려져 있습니다. 그의 웹사이트(javascript.crockford.com)에는 JavaScript에 대한 많은 정보가 있습니다. JavaScript에 관심이 있는 개발자라면 반드시 들러 보아야 할 곳입니다.

클래스로부터 상속
지금까지 생성자 함수가 작동되는 방법과 프로토타입 개체를 사용하여 JavaScript에서 클래스를 시뮬레이트하는 방법을 살펴보았으며 프로토타입 체인을 통해 모든 개체가 Object.prototype의 메서드를 공통적으로 가진다는 것을 확인했습니다. 차단을 사용하여 클래스의 전용 멤버를 시뮬레이트하는 방법도 확인했습니다. 그러나 여기에는 무엇인가가 빠져 있습니다. C#에서는 일상적인 작업이라고 할 수 있는 클래스에서 파생시키는 방법을 아직 설명하지 않았습니다. 아쉽게도 JavaScript에서 클래스를 상속하는 것은 C#에서 콜론을 입력하는 것처럼 단순하지 않으며 그 이상의 작업이 필요합니다. 반면에 JavaScript는 매우 유연하여 클래스에서 상속하는 다양한 방법을 사용할 수 있습니다.
그림 9에서 보여 주는 것처럼 Pet이라는 기본 클래스가 있고 여기에서 파생된 Dog 클래스가 있다고 가정해 보겠습니다. JavaScript에서는 이를 어떻게 구현해야 할까요? Pet 클래스는 간단하며 지금까지 배운 내용으로 구현할 수 있습니다.
그림 9 클래스 
// class Pet
function Pet(name) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
}

Pet.prototype.toString = function() {
    return “This pet’s name is: “ + this.getName();
};
// end of class Pet

var parrotty = new Pet(“Parrotty the Parrot”);
alert(parrotty);
그렇다면 이제 Pet에서 파생된 Dog 클래스를 만들고자 한다면 어떻게 해야 할까요? 그림 9에서 볼 수 있는 것처럼 Dog에는 breed라는 추가 속성이 있으며 Pet의 toString 메서드를 다시 정의하였습니다. C#에서는 메서드와 속성의 이름에 파스칼식 대/소문자가 권장되지만 JavaScript에서는 카멜식 대/소문자를 사용하는 것이 관례입니다. 그림 10에서는 이러한 작동 방식을 보여 줍니다.
여기서 사용된 프로토타입 대체 트릭은 프로토타입 체인을 올바르게 설정하므로 C#에서와 마찬가지로 instanceof 테스트도 작동합니다. 또한 권한 있는 메서드 역시 예상대로 작동합니다.

네임스페이스 시뮬레이션
C++와 C#에서 네임스페이스는 이름 충돌의 우려를 최소화하기 위해 사용됩니다. .NET Framework에서 네임스페이스는 예를 들어 Microsoft.Build.Task.Message 클래스와 System.Messaging.Message 클래스를 구별하는 데 사용됩니다. JavaScript에는 네임스페이스 지원과 관련된 세부적인 언어 지원은 없지만 개체를 사용하여 손쉽게 네임스페이스를 시뮬레이트할 수 있습니다. JavaScript 라이브러리를 만들고자 한다고 가정해 보겠습니다. 함수 및 클래스를 전역으로 정의하는 대신 다음과 같이 네임스페이스 내에 래핑할 수 있습니다.
var MSDNMagNS = {};

MSDNMagNS.Pet = function(name) { // code here };
MSDNMagNS.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Pet(“Yammer”);
한 수준의 네임스페이스는 고유하지 않을 수 있으므로 다음과 같이 중첩된 네임스페이스를 만들 수 있습니다.
var MSDNMagNS = {};
// nested namespace “Examples”
MSDNMagNS.Examples = {}; 

MSDNMagNS.Examples.Pet = function(name) { // code };
MSDNMagNS.Examples.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Examples.Pet(“Yammer”);
짐작할 수 있겠지만 긴 중첩 네임스페이스를 입력하는 일은 금방 성가시게 느껴지게 됩니다. 다행스럽게도 라이브러리 사용자는 짧은 이름으로 네임스페이스에 별칭을 붙일 수 있습니다.
// MSDNMagNS.Examples and Pet definition...

// think “using Eg = MSDNMagNS.Examples;” 
var Eg = MSDNMagNS.Examples;
var pet = new Eg.Pet(“Yammer”);
alert(pet);
Microsoft AJAX 라이브러리의 소스 코드를 살펴보면 라이브러리의 저자 역시 네임스페이스를 구현하기 위해 비슷한 기술을 사용했음을 알 수 있습니다. 정적 메서드 Type.registerNamespace의 구현을 살펴보십시오. 자세한 내용은 보충 기사 "OOP 및 ASP.NET AJAX"를 참조하십시오.

JavaScript에서 이와 같이 코딩해야 할까요?
지금까지 JavaScript에서도 개체 지향 언어를 문제 없이 지원한다는 것을 확인했습니다. 프로토타입 언어로 설계되었지만 JavaScript는 유연하고 강력하므로 널리 사용되는 다른 언어에서 일반적으로 사용되는 클래스 기반 프로그래밍 스타일을 지원할 수 있습니다. 그러나 문제는 JavaScript에서 이와 같이 코딩해야 하는지 결정하는 것입니다. C# 또는 C++에서 코딩하는 방식대로 JavaScript로 코딩하면서 없는 기능은 시뮬레이트하는 것이 현명한 방법일까요? 각각의 프로그래밍 언어는 서로 다르며 한 언어의 최상의 방법이 다른 언어에서는 최상의 방법이 아닐 수 있습니다.
JavaScript에서는 클래스가 다른 클래스에서 상속하는 것과 달리 개체가 다른 개체에서 상속하는 것을 확인했습니다. 따라서 정적 상속 계층을 사용하여 많은 클래스를 만들기는 가능하지만 이것은 JavaScript에 맞는 방식은 아닙니다. Douglas Crockford가 그의 자료 "JavaScript의 프로토타입 상속"에서 밝힌 것처럼 JavaScript에 맞는 프로그래밍 방식은 프로토타입 개체를 만들고 해당 원본 개체에서 상속하는 새 개체 아래에 간단한 개체 함수를 만드는 방식일지도 모르겠습니다.
    function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
JavaScript의 개체는 유연하므로 만든 후에 필요에 따라 새 필드와 새 메서드를 사용하여 손쉽게 개체를 보강할 수 있습니다.
지금까지는 모두 좋지만 전 세계에 있는 개발자 대부분이 클래스 기반 프로그래밍에 익숙하다는 사실은 부인할 수 없습니다. 클래스 기반 프로그래밍은 또한 계속해서 유지될 것입니다. 발표 예정인 버전 4 ECMA-262 규약(ECMA-262는 JavaScript의 공식 사양)에 따르면 JavaScript 2.0에는 진정한 클래스가 추가될 예정입니다. 즉, JavaScript는 클래스 기반 언어로 변화하고 있습니다. 그러나 JavaScript 2.0이 널리 보급되기까지는 몇 년이 걸릴 것입니다. 그 전까지는 현재의 JavaScript로 프로토타입 기반 스타일과 클래스 기반 스타일 코드를 모두 읽고 작성할 수 있다는 사실을 알고 있는 것이 중요합니다.

앞으로의 전망
클라이언트에 크게 의존하는 대화식 AJAX 응용 프로그램이 확산됨에 따라 JavaScript는 빠른 속도로 .NET 개발자를 위한 가장 유용한 도구로 자리를 잡고 있습니다. 그러나 JavaScript의 프로토타입 특성은 C++, C# 또는 Visual Basic과 같은 언어에 익숙해 있던 개발자들에게 다소 생소할 수 있습니다. 필자의 경험을 이야기하자면 과정에 어려움이 없었던 것은 아니지만 JavaScript를 배우는 과정은 충분히 가치가 있었다고 생각합니다. 이 기사를 통해 여러분의 과정을 도울 수 있다면 좋다면 좋겠습니다. 바로 그것이 필자의 목표입니다.
 
 
OOP 및 ASP.NET AJAX
ASP.NET AJAX에 구현된 OOP와 이 기사에서 설명한 정식 구현과는 약간의 차이점이 있습니다. 이러한 차이점이 있는 이유는 두 가지입니다. ASP.NET AJAX 버전에서는 xml-script 및 매개 변수 유효성 검사와 같은 선언적 구문에 필수적인 리플렉션에 대한 더 많은 가능성을 제공하며 ASP.NET AJAX는 .NET을 사용하는 개발자에게 익숙한 속성, 이벤트, 열거형 및 인터페이스와 같은 몇 가지 추가 구조를 JavaScript에 제공하는 데 초점을 맞추고 있습니다.
현재 널리 사용 가능한 버전의 경우 JavaScript에는 .NET 개발자에게 익숙한 몇 가지 핵심 OOP 개념이 빠져 있지만 ASP.NET AJAX에서는 이러한 대부분의 개념을 에뮬레이트합니다.
클래스에 명명 규칙 기반의 속성 접근자(예는 조금 뒤에 있음)는 물론 .NET에서 제공되는 패턴을 비슷하게 따르는 멀티캐스트 이벤트를 추가할 수 있습니다. 전용 변수는 밑줄로 시작하는 멤버는 전용이라는 관례에 바탕을 두고 있습니다. 진정한 전용 변수가 필요한 경우는 드물지만 이 정책은 디버거에서 이러한 변수를 검사할 수 있도록 해 줍니다. 일반적인 오리 형식 지정을 벗어난 형식 검사 시나리오를 지원하기 위해 인터페이스가 추가되었습니다. 오리 형식 지정이란 어떤 것이 오리처럼 걷고 오리 같은 소리를 낸다면 이것은 오리이거나 최소한 오리와 같이 취급해야 한다는 개념에 바탕을 둔 것입니다.

클래스와 리플렉션
JavaScript에서 함수의 이름을 알아낼 수 있는 방법은 없습니다. 이것이 가능하다고 하더라도 네임스페이스 변수에 익명 함수를 할당함으로써 클래스 생성자를 작성하는 대부분의 상황에는 큰 도움이 되지 않을 것입니다. 실제 형식 이름을 구성하는 것은 동등한 수준으로 액세스가 불가능하며 생성자 함수 자체가 아무런 정보도 없는 이 변수의 정규화된 이름입니다. 이러한 한계를 해결하고 JavaScript 클래스에 대한 풍부한 리플렉션을 제공하기 위해 ASP.NET AJAX에서는 형식의 이름을 등록하도록 요구하고 있습니다.
ASP.NET AJAX의 리플렉션 API는 기본 제공 형식, 클래스, 인터페이스, 네임스페이스는 물론 열거형을 포함하여 어떤 형식에 대해서도 작동하며 런타임에 클래스 계층을 검사하기 위한 isInstanceOfType 및 inheritsFrom와 같은 .NET Framework와 비슷한 함수도 포함합니다. ASP.NET AJAX는 또한 디버그 모드에서 약간의 형식 검사를 수행하여 개발자가 더 일찍 버그를 찾을 수 있도록 지원합니다.

클래스 계층 및 호출 기반 등록
ASP.NET AJAX에서 클래스를 정의하려면 해당 생성자를 변수에 할당해야 합니다. 생성자가 기반을 호출하는 방법을 확인하십시오.
MyNamespace.MyClass = function() {
    MyNamespace.MyClass.initializeBase(this);
    this._myProperty = null;
}
Then, you need to define the class members itself in its prototype:

MyNamespace.MyClass.prototype = {
    get_myProperty: function() { return this._myProperty;},
    set_myProperty: function(value) { this._myProperty = value; },
    doSomething: function() {
        MyNamespace.MyClass.callBaseMethod(this, “doSomething”);
        /* do something more */
    }
}
이제 최종적으로 클래스를 등록하게 됩니다.
MyNamespace.MyClass.registerClass(
    “MyNamespace.MyClass “, MyNamespace.BaseClass);
생성자와 프로토타입 계층은 registerClass 함수에 의해 자동으로 관리되므로 직접 관리할 필요가 없습니다.

Bertrand Le Roy는 ASP.NET AJAX 팀의 소프트웨어 설계 엔지니어 II입니다.
신고
Trackback 0 And Comment 0

eclipse 단축키

|
 CTRL + D
 현재 행을 삭제 한다
 ALT + UP/DOWN
 현재 행을  한줄 위나 아래로 이동시킨다.
 ALT + LEFT/RIGHT
 에디터에서 바로 이전에 편집햇던 위치로 이동한다. ALT + RIGHT 로 다시 돌아올 수 있다.
 CTRL + 1
 현재 커서 위치에서 가능한 기능 들을 보여준다. (예, field 나 variable 생성하기) 
 CTRL + F6
 열려 있는 에디터간 이동하기
 CTRL + F7
 열려 있는 뷰간에 이동하기 
 CTRL + F8
 퍼스펙티브간에 이동하기
 CTRL + F11  현재 선택된 클래스나 페이지를 바로 실행한다. (CTRL 없이 F11 을 누르면 디버그 모드로 실행된다.)
 CTRL + N   새로 생성하기 위자드를 띄운다. 새로운 프로젝트나 클래스 파일등을 새로 생성할 수 있다. (이것보단 CTRL + 3 단축키를 사용하는 것이 더 났다.)
 CTRL + M  현재 열려있는 뷰나 에디터를 최대화 한다. 
 CTRL + I  들여쓰기 (indent) 를 수정한다. 
 CTRL + SHIFT + F   소스를 자동으로 포맷 한다.
 CTRL + J  검색 창을 따로 띄우지 않고도 찾을 키워드를 입력하면 바로 검색이 실행된다. UP / DOWN 방향키로 다음 일치하는 곳으로 이동할 수 있다.( CTRL + SHIFT + J 는 반대 방향으로 찾기를 수행한다.)
 CTRL + SHIFT + O 
 자바 에디터에서 import 들을 정리해준다.
 CTRL + SHIFT + L   단축키 목록을 보여주고 바로 실행할 수 있다. 
 ALT + SHIFT + V   자바 에디터에서 현재 선택된 field, method 를 다른 클래스로 이동한다. 
 ALT + SHIFT + M   자바 에디터에서 현재 선택된 블럭을 메소드로 추출해낸다.
 SHIFT + ALT + UP   Java/HTML 에디터에서 현재 커서 위치의 상위 요소를 블럭으로 선택한다. 
  SHIFT + ALT + LEFT/RIGHT  Java/HTML 에디터에서 현재 커서 위치의 다음(RIGHT) 이나 이전(LEFT) 위치의 요소를 블럭으로 선택한다. 
 SHIFT + ALT + DOWN   Java/HTML 에디터에서 이전 선택 영역으로 되돌린다.
  CTRL + 3  이클립스의 모든 명령을 바로 실행할 수 있다. ( 예. 새로운 클래스를 생성하려면 'new class' 만 치고  Enter 만 치면 된다. Naviator 뷰를 띄우고 싶다면 'navigator' 만 치고 Enter 를 치면 된다.) 

****** 이클립스 사용팁 및 단축키 모음 *******

 

===== 실행 =====
1. Ctrl + F11 : 바로 전에 실행했던 클래스 실행


===== 소스 네비게이션 =====
1. Ctrl + 마우스커서(혹은 F3) : 클래스나 메소드 혹은 멤버를 상세하게 검색하고자 할때
2. Alt + ->, Alt + <- : 이후, 이전
3. Ctrl + o : 해당 소스의 메소드 리스트를 확인하려 할때
4. F4 : 클래스명을 선택하고 누르면 해당 클래스의 Hierarchy 를 볼 수 있다.


===== 문자열 찾기 =====
1. Ctrl + k : 찾고자 하는 문자열을 블럭으로 설정한 후 키를 누른다.
2. Ctrl + Shift + k : 역으로 찾고자 하는 문자열을 찾아감.
3. Ctrl + j : 입력하면서 찾을 수 있음.
4. Ctrl + Shift + j : 입력하면서 거꾸로 찾아갈 수 있음.
5. Ctrl + f : 기본적으로 찾기

6. Ctrl + shift + g : 커서를 메소드 위에 놓고 누르면 해당 메소드를 사용하는 곳을 찾아준다. 마우스 오른쪽-Reference - 에도 있음.


===== 소스 편집 =====
1. Ctrl + Space : 입력 보조장치(Content Assistance) 강제 호출 => 입력하는 도중엔 언제라도 강제 호출 가능하다.
2. F2 : 컴파일 에러의 빨간줄에 커서를 갖져다가 이 키를 누르면 에러의 원인에 대한 힌트를 제공한다.
3. Ctrl + l : 원하는 소스 라인으로 이동
   로컬 히스토리 기능을 이용하면 이전에 편집했던 내용으로 변환이 가능하다.
4. Ctrl + Shift + Space : 메소드의 가로안에 커서를 놓고 이 키를 누르면 파라미터 타입 힌트를 볼 수 있다.

5. 한줄 삭제 CTRL + D
6. 파일 닫기 : CTRL+W 
7. 들여쓰기 자동 수정. (3.0 NEW) : CTRL+I 
8. 블록 주석(/*..*/) 추가.(3.0 NEW): CTRL+SHIFT+/ 
  8.1 Ctrl + / 해주면 여러줄이 한꺼번에 주석처리됨. 주석 해제하려면 반대로 하면 됨.
9. 위(아래)줄과 바꾸기 : ALT+UP(DOWN) 

10. 블록 선택하기.  : ALT+SHIFT+방향키 
11. 메소드의 파라메터 목록 보기. : CTRL+SHIFT+SPACE
12. 자동으로 import 하기 : CTRL+SHIFT+O 

13. 열린 파일 모두 닫기 : CTRL + SHIFT + F4
14. 블록 주석 제거 : CTRL+SHIFT+
15. 전체화면 토글 : CTRL+M 
16. 한줄(블럭) 복사 : Ctrl + Alt + 위(아래) 
17. 다음 annotation(에러, 워닝, 북마크 가능)으로 점프 : Ctrl + , or . 
18. 퀵 픽스 : Ctrl + 1  
19. 메소드 정의부로 이동 : F3
20. 하이어라키 팦업 창 띄우기(인터페이스 구현 클래스간 이동시 편리) : Ctrl + T  

21. 메소드나 필드 이동하기 CTRL + O 
22. ULTRAEDIT나 EDITPLUS 의 CTRL+TAB 과 같은 기능. : CTRL+F6 

===== 템플릿 사용 =====
1. sysout 입력한 후 Ctrl + Space 하면 System.out.println(); 으로 바뀐다.
2. try 입력한 후 Ctrl + Space 하면 try-catch 문이 완성된다.
3. for 입력한 후 Ctrl + Space 하면 여러가지 for 문을 완성할 수 있다.
4. 템플릿을 수정하거나 추가하려면 환경설정/자바/편집기/템플리트 에서 할 수 있다.

===== 메소드 쉽게 생성하기 =====
1. 클래스의 멤버를 일단 먼저 생성한다.
2. override 메소드를 구현하려면 : 소스->메소드대체/구현 에서 해당 메소드를 체크한다.
3. 기타 클래스의 멤버가 클래스의 오브젝트라면 : 소스->위임메소드 생성에서 메소드를 선택한다.


===== organize import =====
1. 자바파일을 여러개 선택한 후 소스 -> 가져오기 체계화 해주면 모두 적용된다.


===== 주석 처리 =====
2. Ctrl + / 해주면 여러줄이 한꺼번에 주석처리됨. 주석 해제하려면 반대로 하면 됨.


===== 소스 코드 형식 및 공통 주석 설정 =====
1. 환경설정 -> 자바 -> 코드 스타일 -> 코드 포멧터 -> 가져오기 -> JYJ프로파일.xml 을 불러다가 쓰면 된다.
2. 또한 다수의 자바파일에 프로파일을 적용하려면 패키지 탐색기에서 패키지를 선택한 후 소스 -> 형식화를 선택하면 된다.
3. 환경설정 -> 자바 -> 코드 스타일 -> 코드 템플리트 -> 가져오기 -> JYJ템플리트.xml 을 불러다가 쓰면 된다.


===== 에디터 변환 =====
1. 에디터가 여러 파일을 열어서 작업중일때 Ctrl + F6 키를 누르면 여러파일명이 나오고 F6키를 계속 누르면 아래로
2. Ctrl + Shift + F6 키를 누르면 위로 커서가 움직인다.
3. Ctrl + F7 : 뷰간 전환
4. Ctrl + F8 : 퍼스펙티브간 전환
5. F12 : 에디터로 포커스 위치


===== JAVA Doc 생성 =====
1. 프로젝트 -> Javadoc 생성


===== 내보내기 =====
2. 해당 프로젝트를 zip 행태로 압축해서 보관할 수 있다.


===== 자바 찾아보기 perspective =====
3. 해당 프로젝트를 보다 편리하게 한번 둘러보는데 좋다.


===== 스크랩북 =====
1. 스크랩북을 이용하면 자바파일을 만들어 테스트 해보지 않고도 간단하게 테스트 해 볼 수 있다.
2. 패키지 탐색기에서 신규 -> 기타 -> 자바 -> 자바 실행/디버그 -> 스크랩북 페이지 선택


===== 디버그 =====
1. F5(Step Into) : 현재의 명령문이 호출되는 메소드 속으로 진행하여, 그 첫 문장을 실행하기 전에 멈춘다.
  하지만 자바 라이브러리 클래스 수준까지 들어가므로 단계필터 사용을 체크(Shift+F5)를 하면 필터를 설정한 
  클래스에 대하서는 Step Over 기능과 같은 기능을 수행한다.
2. F6(Step Over) : 현재의 명령문을 실행하고 다음 명령문 직전에 다시 멈춘다.
3. F7(Step Return) : 현재의 메소드에서 리턴한 직후에 다시 멈춘다.
4. F8(Resume) : 멈추어 있던 쓰레드를 다시 계속 실행한다.
5. Display view(표시) : 창 -> 보기표시 -> 표시 선택하여 소스상에서 필요한 부분을 선택해서 실행시켜 볼 수 있다.
  한 순간의 값만 필요할 때 볼 수 있는 반면에 아래놈은 계속적으로 값이 변하는 것을 확인 할 수 있다.
6. Expression view(표현식) : 복잡한 식 혹은 객체를 디버깅 하면서 계속 볼 수있는 창이다.
  마우스 오른버튼 -> 감시 표시식 추가 선택한 후 사용한다.
  환경설정 -> 자바 -> 디버그 -> 세부사항 포멧터 선택 후 보기 편한 식으로 편집하면 Expression View 에서 결과를 
  실시간으로 확인 할 수 있다.
7. 디버깅 하면서 소스를 수정하고 프로그램을 다시 시작할 필요 없이 계속 디버깅을 진행 할 수 있다.
  다만 메소드를 추가하거나 필드를 추가하면 프로그램을 다시 시작해야 한다.
8. Drop to Frame(프레임에 놓기) 는 정말 모르겠다.
9. 디버깅 작업을 할때 해당 라이브러리의 소스코드를 첨부하지 않으면 진행상황을 볼 수 없을 경우
  해당 라이브러리에 소스코드를 첨부할 수 있다. 해당 프로젝트 -> 특성 -> Java 빌드경로 -> 라이브러리 -> 소스참조
  편집 버튼을 눌러서 첨부 한다.


===== 도움말 =====
"JDT 팁과 요령"이라고 치면 여러가지 흥미로운 팁이 나온다.


===== JUnit =====
별로 추천하고 싶지 않은 툴임.


===== Refactoring =====
1. Pull Down : 수퍼클래스의 멤버나 메소드를 하위 클래스로 내리는 행위
2. Push Up : 하위 클래스의 멤버나 메소드를 상위 클래스로 올리는 행위


===== CVS =====
1. CVS 서버로는 윈도우에 CVSNT 최신버젼을 설치하고, [컴퓨터 관리]를 통해서 사용자를 추가한다.
- CVSNT 를 설치할때 윈도우의 경우 Repositories 를 추가할때 접두어를 사용하지 말고 디렉토리 풀이름(d:/cvs) 이런식
  으로 등록해야 Eclipse 와 에러없이 동기화가 잘 된다.
2. Eclipse 에 CVS 저장소 perspective 에서 새로운 저장소를 등록한 후 HEAD 에서 해당 프로젝트를 체크아웃하면
  자바 perspective 에 등록되는 것을 확인할 수 있다.


===== 웹 프로그램밍 =====
1. WTP를 다운받아서, 기존 이클립스 프로그램에 붙여넣는다.

2. 프로젝트를 생성할때 WEB-> Dynamic Web Project로 생성한다.

3. 기본 라이브러리에 TOMCAT, JDK 관련 라이브러리가 추가된 것을 확인한다.

 

 

이클립스 메모리 문제 : 등록정보에서 대상에

C:\eclipse\eclipse.exe -clean -vmargs -Xmx512M 넣어준다.

-clean : 실행이 될때 플러그인을 다시 한번 리로딩을 해달라는 의미

 

메뉴 source -> generate constructor using field  : 생성자를 자동으로 만들어준다.

alt + shift + s -> 메뉴 source가 뜬다.   get, set메서드들도 자동으로 만들어 준다.

 

F5 : refresh 기능

F4 : 하나의 클래스의 하위 클래스 구조를 보여준다...

 

ctrl + shift + o : 필요한 import 문을 모두 넣어준다.

 

ctrl + alt + h : 이 메소드를 어디에서 호출하고 있는가

 

alt + shift + r : 메소드 리네임

 

 

- 이클립스에서 톰캣 설정 tomcatPluginV32beta3.zip, apache-tomcat-5.5.17-admin.zip-


window -> Preferences -> Tomcat에서
Tomcat version을 설정하고 Tomcat Home을 설정 Apply 
Tomcat 하위의 Advanced에도 Tomcat base을 설정 Apply
Tomcat Manager 에서 username과 password 에 admin으로 설정한다. 그러면 conf폴더 밑에 
tomcat-users.xml 에 마지막 라인이 추가 된것을 확인 할 수 있다.

 

- 존재하는 프로젝트 열기 -
  File -> Import를 사용 General 에서 Exisiting Projects into Workspace -> 폴더를 추가 -> Finish

 

- api 추가 -
프로젝트 오른쪽 마우스 -> Properties - > Java Build Path -> Tomcat_Home을 활성화 -> Javadoc location -> Edit에서 api 위치설정

 

- html 편집창 텍스트 조절 -
Window -> Preferences -> General -> Appearance -> Colors and Fonts -> Structured Text Font -> Change로 조절

 

- xml 편집창 텍스트 조절 -
Window -> Preferences -> Editors -> ADD 에서 *.xml를 추가한다. 그리고 아래의 Add  Text Editer를 추가한다.

 

 

Eclipse 단축키 확인 및 변경

 

Eclipse의 모든 단축키는 Window >> Preferences >> Workbench >> Keys 메뉴에서 확인 및 변경이 가능하다. 그 동안 다른 툴에서 자신의 손에 익숙한 단축키들이 있다면 이 메뉴에서 단축키들을 변경하는 것이 가능하다.

 

 

Java Editor 단축키

 

Ctrl + Shift + M : 특정 클래스 Import 시키기
Ctrl + Shift + O : 자동으로 Import 시키기
Ctrl + Shift + F : 코드 자동 정리
Ctrl + Shift + G : 특정 메써드나 필드를 Reference하고 있는 곳을 찾는다.
Ctrl + 1 : Quick Fix. 에러가 발생했을 경우 Quick Fix를 통해 쉽게 해결이 가능하다.
Ctrl + Shift + / : 블럭 주석 설정
Alt + Shift + UP : 커서를 기준으로 토큰단위 블럭지정
Alt + Shift + DOWN : 커서를 기준으로 토큰단위 블럭해제
CTRL + L : 특정 줄번호로 가기
Alt + Shift + J : 자동으로 주석 달기 (메소드나 멤버변수에 포커스 두고 실행)

 

 

Window 이동

 

F10 : 메뉴창을 활성화
Ctrl + F8 : 다음 Perspective로 이동
Ctrl + N : 새로운 파일 및 프로젝트 생성.
Ctrl + Shift + Down : Java Editor에서 다음 member로 이동.
Ctrl + F7 : 다음 View로 이동.
Ctrl + Shift + F7 : 이전 View로 이동.
Alt + <- : 이전 작업 화면
Alt + -> : 다음 작업 화면
F12 : 컴파일 중 에러등으로 포커스가 다른데로 갔을 때 Editor 로 커서 이동
Ctrl + 1 : 컴파일 에러가 발생한 곳에서 Ctrl + 1을 누를 경우 컴파일 에러에 대한 해결책을 제시

 

디버깅 단축키

 

CTRL + Shift + B : 현 커서의 위치에 브레이크 포인터 설정/해제
F11 : 디버깅 시작
F8 : 디버깅 계속
F6 : 한줄씩 실행(Step Over)
F5 : 한줄씩 실행하되 함수일 경우 그 함수 내부로 들어감(Step Into)
CTRL + R : 현재 라인까지 실행(Run to Line)

 

 

Refactoring 단축키

 

Shift + ALT + 알파벳 : Refactoring을 위한 단축키 임.

 

 

RUN 단축키

 

Ctrl + F11 : 이전에 실행되었던 Run파일 실행.


신고

'개발/활용정보 > Java' 카테고리의 다른 글

Ant 태스크  (0) 2011.04.19
Creating a Custom Event  (0) 2011.04.19
eclipse 단축키  (0) 2011.04.13
자바가 사용하는 메모리의 종류와 특징  (0) 2011.04.13
Java 메모리 설정 MaxPermSize  (0) 2011.04.13
Jboss Clustering  (0) 2011.04.13
Trackback 0 And Comment 0

자바가 사용하는 메모리의 종류와 특징

|

http://drkein.tistory.com/95

 

상황인지 미들웨어를 개발하다 보니.. 
성능면에서 등록 가능한 룰의 갯수를 측정해 본적이 있다.
그런데, 약 8만개 정도의 룰을 등록하니 OutOfMemory 에러가 발생.. 
일단 jvm 에서 -Xmx1024로 메모리를 늘려 잡아서 10만개 까지 룰 등록을 하긴 했는데.. 
원인을 알아야 해결을 할 수 있을것 같다.. 

자... 
자바가 사용하는 메모리의 종류엔 뭐가 있을까... 
얼핏 떠오르는게 힙 메모리 밖에 없다..  (근데, Heap 메모리는 뭐지? )

일단 여기저기에서 찾아낸 자바 메모리 관련 정보를 정리해 보자.
일단 자바가 사용하는 메모리의 종류를 알아보고
메모리 관리 방법(Garbage collection 방법)을 파악한 뒤
내 application 에 맞는 최적화 방법을 찾아내는 방법으로 메모리 문제에 접근하는게 순서 인것 같다.

(자바의 garbage collection 관련 링크 : http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html )

1. 자바 VM은 힙을 세개의 영역으로 나누어 사용한다.
  1) New/Young 영역 : 새로 생성된 객체를 저장
  2) Old 영역 : 만들어진지 오래된 객체를 저장
  3) Permanent 영역 : JVM클래스와 메서드 객체를 저장

자바가 사용하는 메모리 구조


여기서 New 영역은 다시  
 a) Eden : 모든 새로 만들어진 객체를 저장
 b) Survivor Space 1, Survivor Space 2 : Old 영역으로 넘어가기 전 객체들이 저장되는 공간
으로 구분된다.

2. Garbage Collector
자바 언어의 중요한 특징중 하나.
전통적인 언어의 경우 메모리를 사용한 뒤에는 일일이 메모리를 수거해 주어야 했다. 
그러나, 자바 언어 에서는 GC기술을 사용하여 개발자로 하여금 메모리 관리에서 자유롭게 했다.

자바의 GC는 New/Young 영역과 Old 영역에 대해서만 GC를 수행한다.  
(Permanent 영역은 code가 올라가는 부분이기 때문에 GC가 필요없다.)

1) Minor GC
New 영역의 GC를 Minor GC라고 부른다. New 영역은 Eden과 Survivor라는 두 영역으로 구분된다.
Eden 영역은 자바 객체가 생성 되자 마자 저장이 되는 곳이다. 
이곳의 객체가 Minor GC가 발생할 때 Survivor 영역으로 이동된다.

Survivor 영역은 Survivor1 과 Survivor2로 나뉘어 지는데, Minor GC가 발생하면 Eden과 Survivor1에 살아있는 객체가 Survivor2로 이동되고, Eden 영역과 Survivor1 영역에 남아있는(죽어있는) 객체는 clear된다.
결과적으로, 현재 살아있는 객체들만 Survivor2에 남아있게 된다.
다음번 Minor GC가 발생되면 같은 원리로, Eden과 Survivor2의 살아있는 객체가 Survivor1으로 이동되고, 두 영역은 Clear 된다.
이와 같은 방법으로 반복되면서 메모리를 수거한다. 이런 방식의 GC알고리즘을 Copy & Scavenge라고 한다. 속도가 빠르며 작은 크기의 메모리를 collecting 하는데 효과적이다.
Minor GC 과정중 오래된 객체는 Old 영역으로 복사된다.  
(Kein:얼마나 지나야 '오래된' 객체인 것인지는 명확히 모르겠네요)


2) Full GC
Old 영역의 GC를 Full GC라 한다. Mark & Compact 알고리즘을 이용하는데, 전체 객체들의 reference를 따라가면서 연결이 끊긴 객체를 marking 한다. 이 작업이 끝나면 사용되지 않는 객체가 모두 mark 되고, 이 객체들을 삭제한다. 
실제로는 삭제가 아니라, mark 된 객체로 생기는 부분을 unmark된, 즉 사용중인 객체로 메꾸는 방법이다.

Full GC는 속도가 매우 느리며, Full GC가 일어나는 도중에 순간적으로 java application이 멈춰버리기 때문에 Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 application의 성능과 안정성에 매우 큰 영향을 미치게 된다.

Full GC 동작 순서


3. Garbage Collection이 중요한 이유
Minor GC는 보통 0.5초 이내에 끝나기 때문에 큰 문제가 되지 않는다. 하지만, Full GC의 경우 보통 수 초가 소요되고, GC동안 Application이 멈추기 때문에 문제가 될 수 있다. 5초 동안 서버가 멈춘다면, 멈춰있는 동안 사용자의 request는 쇄도하게 되고, queue에 저장되었다가 요청이 한꺼번에 들어오게되면 여러 장애를 발생할 수 있게 된다.
원할한 서비스를 위해서 GC를 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 변수로 작용하게 된다.


4. Garbage Collection 알고리즘들
1) Default Collector
위에 설명한 전통적인 GC방법으로 Minor GC에 Scavenge를, Full GC에 Mark & Compact를 사용하는 방법이다.

2) Parallel GC
JDK 1.3까지는 하나의 thread 에서만 GC가 수행되었다. JDK 1.4 부터 지원되는 parallel gc 는 minor gc를 동시에 여러개의 thread 를 이용해서 수행하는 방법으로 하나의 thread 에서 gc를 수행하는 것보다 빠른 gc를 수행한다.


하지만, parallel gc가 언제나 유익한 것은 아니다. 1 CPU에서는 오히려 parallel gc 가 느리다. multi thread에 대한 지원이나 계산등을 위해서 4CPU의 256M 정도의 메모리를 보유한 시스템에서 유용하게 사용된다.
parallel gc 는 두가지 옵션을 제공하는데, Low-Pause 방식과 Throughput 방식이다. 
solaris 기준으로 Low-pause 방식은 ?XX:+UseParNewGC 옵션을 사용한다. Old GC를 수행할 때 Application 이 멈추는 현상을 최소화 하는데 역점을 두었다. 
Throughput 방식은 ?XX:+UseParallelGC 옵션을 사용하며, Old 영역을 GC할때는 기본 옵션을 사용하며 Minor GC가 발생했을 때 최대한 빨리 수행되도록 throughput에 역점을 둔 방식이다.

3) Concurrent GC
Full GC를 하는 동안 시간이 길고, Application이 순간적으로 멈추는 현상이 발생하는 단점을 보완하기 위해서, Full GC에 의해 Application이 멈추는 현상을 최소화 하기 위한 방법이다.
Full GC에 소요되는 작업을 Application을 멈추고 하는것이 아니라, 일부는 Application을 수행하고, Application이 멈추었을때 최소한의 작업만을 GC에 할당하는 방법으로 Application이 멈추는 시간을 최소화 한다.
Application이 수행중 일 때 (붉은라인) Full GC를 위한 작업을 수행한다. Application이 멈춘 시간동안에는 일부분의 작업을 수행하기 때문에 기존 Default 방법보다 멈추는 시간이 현저하게 줄어든다.
solaris JVM 에서는 -XX:+UseConcMarkSweepGC 옵션을 사용한다.

4) Incremental GC (Train GC)
Incremental GC 또는 Train GC 라고 불리우는 방법은 JDK 1.3 부터 지원된 방법이다. 의도 자체는 Full GC 동안 Application이 멈추는 시간을 최소화 하는데 목적이 있다.
Minor GC가 일어날 때 마다 Old 영역을 조금씩 GC를 해서, Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.
그림에서 보듯, 왼쪽의 Default GC Full GC가 일어난 후에나 Old 영역이 Clear된다그러나오른쪽의 Incremental GC를 보면 Minor GC가 일어난후에, Old 영역이 일부Collect된것을 알 수 있다.
Incremental GC
를 사용하는 방법은 JVM 옵션에 ?Xinc 옵션을 사용하면 된다
.
Incremental GC
는 많은 자원을 소모하고, Minor GC를 자주일으키며Incremental GC를 사용한다고 Full GC가 없어지거나 그 횟수가 획기적으로 줄어드는 것은 아니다오히려 느려지는 경우가 많다.

5. GC 로그 수집 및 분석 방법
이제 적군에 대해 알았으니 나 자신을 파악할 차례다. 
내 Application의 gc 동태를 파악하기 위해 java 실행 옵션에 -verbose:gc 옵션을 주면 gc 로그를 출력할 수 있다. 

garbage collection 로그

로그중 GC 는 Minor GC 이고, Full GC는 Full GC를 나타낸다. 
그 뒤의 숫자는 GC수행 전 heap 메모리 사용량 이다. (New + Old + Perm 영역)
그뒤 -> 이후의 숫자는 GC 수행 후 heap 메모리 사용량을 나타낸다. 
Minor GC 가 수행된 뒤에는 Eden 과 Survivor 영역의 GC가 수행된 것이며, GC이후 heap 사용량은 Old영역의 용량과 유사하다. 괄호 안의 Total Heap Size 는 현재 jvm 이 사용하는 Heap memory의 양이다. 이 크기는 java 실행 옵션의 -Xms -Xmx 옵션으로 설정이 가능한데, 예를 들어 -Xms512 -Xmx1024로 해 놓으면 jvm는 메모리 사용량에 따라서 512~1024m 사이에서 적절하게 메모리 사용량을 늘였다 줄였다 하며 동작한다.
그 다음값은 gc에 소요된 시간이다. 

위의 로그를 보면, Minor GC가 일어날 때 마다 약 20,000Kbytes 정도의 collection이 일어난다. Minor GC는 Eden과 survivor 영역 하나를 gc 하는 것이기 때문에  New 영역을 20,000Kbyte 정도로 생각할 수 있다. 
Full GC 때를 보면 약 44,000Kbytes 에서 1,749Kbytes 로 줄어든 것을 볼 수 있다. Old 영역에 큰 데이터가 많지 않은 경우이다. 
Data를 많이 사용하는 Application의 경우 전체 Heap 이 512M 라 할 때, Full GC 후에도 480M 정도로 유지되는 경우가 있다. 이런 경우에는 실제로 Application이 메모리를 많이 사용하는 경우라고 판단할 수 있기 때문에, 전체 Heap 메모리를 늘려주면 효과적이다. 

gc 로그를 분석하여 OutOfMemory가 발생하는 시점을 추적할 수 있다.
위의 설명처럼 Full GC 이후 Heap 메모리가 부족한 경우 전체 heap 을 조정하면 되고
Minor gc 도중 New 또는 survivor의 영역에 부족하여 에러가 발생하는 경우
new 또는 survivor의 영역을 확장하여 문제를 해결할 수 있다.


그럼 이제
내 어플리케이션의 gc 로그를 통해서 어떤 방법이 좋은 것인지 분석해 봐야 겠다

신고

'개발/활용정보 > Java' 카테고리의 다른 글

Creating a Custom Event  (0) 2011.04.19
eclipse 단축키  (0) 2011.04.13
자바가 사용하는 메모리의 종류와 특징  (0) 2011.04.13
Java 메모리 설정 MaxPermSize  (0) 2011.04.13
Jboss Clustering  (0) 2011.04.13
Java theory and practice: 메모리 누수와 약한 참조  (0) 2011.04.13
Trackback 1 And Comment 0
prev | 1 | ··· | 78 | 79 | 80 | 81 | 82 | next

티스토리 툴바