AOP 프레임워크 이해와 개발

AOP(Aspect Oriented Programming), 관점지향 프로그래밍은 OOP(Object Oriented Programming) 에게 ‘관심사’라는 관점을 더해 객체 지향 프로그래밍의 변경 없이 다른 관점의 구현을 추가할 수 있다. 더 쉽게 말하면 클래스나 메서드가 동작할 때 코드의 변경 없이 원하는 동작을 추가하는 기법이다.

흔히 AOP 의 예를 들때 ‘로깅(Logging)’ 을 든다. 기존 코드의 변경 없이 코드 본문이 실행 되기전 매개변수 값 등을 로깅하도록 하는 것이다. 물론 로깅 이외에 다양한 용도로 사용되는데, 비즈니스 로직의 검증이나 응용 프로그램 전역적으로 공통적인 관심사 분리에 사용된다.

AOP 프로그래밍의 활용 예

  • 로깅
  • 유효성 검사
  • 트랜잭션 처리
  • 인증/보안 등

AOP 구현 방법

AOP 프레임워크 개발은 비교적 고급 기술에 속한다. AOP 를 이해하고 쓰는 사람들은 많지만 내부 구현까지 이해하는 사람은 드문 것이 사실이다. 그리고 내부 구현을 이해해도 직접 만드는 것은 또 다른 이야기일 것이다.

필자는 어려운 이야기일 수 있는 이 부분에 대해 언급하고자 한다. AOP 프레임워크는 구현 방법이 매우 다양한데, 크게 두 가지 방법으로 요약할 수 있다. (더 자세한 분류는 이 링크를 참고)

AOP 프레임워크 구현 방안

  1. 런타임(Runtime) 구동 방식
    흔히 Dynamic Proxy 라고 하는데, 동적으로 프록시 패턴을 구현하는 객체를 생성해 내는 기법이다. 메모리에 직접 인스트럭션(Instruction) 을 쓰는데 언어마다, 그리고 컴파일 옵션에 따라 인스트럭션이 다를 수 있다. 따라서 최적화에 따라 성능을 좌지우지 한다.
    C# 에서는 MSIL(Microsoft Intermediate Language), 자바는 바이트코드(Bytecode), C/C++은 어셈블리(Assembly) 코드를 메모리에 생성하는 방식이다.
  2. 빌드 타임(Build-time) 구동 방식
    이 방식은 빌드 프로세스를 지원하는 언어에서 가능한 방식이다. 실제 작성한 코드를 컴파일 하기 전에 AOP 의 위빙(Weaving) 코드를 삽입하고 나중에 컴파일 하는 방식을 말한다. C# 에서는 PostSharp, 자바에는 AspectJ 가 대표적이고, 대체적으로 런타임 구동 방식에 비해 성능이 좋다.

AOP 구현

우선 필자가 간단하게 만든 SimpleAop 라이브러리를 참고해 보는 것이 좋겠다. 예전에 만든 이 링크 참고하면 많은 도움이 될 것 같다. 그리고 자바스크립트로 구현한 Javascript OOP-AOP-IoC 도 참고하면 좋다.

프로그래밍이란 무엇인가?

오로지 CPU 입장에서 본다면 프로그래밍은 이미 정의된 함수를 어떻게 호출할 것인가로 귀결된다. CPU 아키텍처에 따라 다르지만 대부분 함수 매개변수는 스택의 로드(Load)와 (Push) 로 구현된다. 그리고 매개변수가 적재되면 Call 인스트럭션을 보내 함수를 호출하는 것이다.

물론 모든 언어가 이에 해당하는 것은 아니다. 아름다움을 추구하는 오브젝티브-C 언어 1/ 2- 언어적 특성 은 조금은 다른 메커니즘으로 동작한다.

구현

첫 번째, 모든 함수는 return 인스트럭션을 가진다. 흔히 void 함수는 return 이 없어도 되지만 컴파일 된 코드(MSIL, Bytecode, Assembly 등)은 함수의 마지막은 항상 return 으로 종료된다. return 의 의미는 함수를 종료하는 것이 아니라 나를 호출한 caller 에게 되돌아 가라는 의미이다.

두 번째, 객체 지향 프로그래밍에서 상속한 클래스의 생성자는 항상 부모 객체를 먼저 생성한다. 다음의 코드를 보면 조금은 더 이해하기 쉬울 것이다.

foreach (var constructor in _implementationType.GetConstructors())
{
	var constructorTypes = constructor.GetParameters().Select(o => o.ParameterType).ToArray();
	var c = _typeBuilder.DefineConstructor(
		MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName,
		CallingConventions.Standard,
		constructorTypes);
	
	var il = c.GetILGenerator();
	il.Emit(OpCodes.Ldarg_0);

	for (var i = 0; i < constructorTypes.Length; i++)
	{
		il.Emit(OpCodes.Ldarg, i + 1);
	}

	il.Call(constructor);
	il.Emit(OpCodes.Nop);
	il.Emit(OpCodes.Ret);
}

세 번째, SimpleAop 에서는 런타임에 프록시 패턴을 구현하는 방식으로 AOP 를 구현하였다. 프록시 패턴을 런타임에 구현하기 위해서는 원본 대상이 필요한데, 이는 인터페이스(Interface) 정의를 사용한다. 대부분의 테스팅 프레임워크의 Mock 객체들은 인터페이스가 필요한데, 바로 프록시를 생성하기 위한 대상으로 사용되기 때문이다.

특히 자바에서는 기본적으로 virtual 메서드이기 때문에, 런타임에 클래스는 override 하기 용이하다. 반면 C# 언어는 virtual 메서드가 아니기 때문에, virtual 로 선언된 대상 클래스가 필요할 수도 있다. 이는 AOP 프레임워크 마다 구현 방법도 다르기 때문에 사용할 AOP 프레임워크가 어떤 방식인지 알아두면 좋을 것이다.

foreach (var method in _interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
    var methodTypes = method.GetParameters().Select(o => o.ParameterType).ToArray();
    var m = _typeBuilder.DefineMethod($"{method.Name}",
        MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig |
        MethodAttributes.Virtual | MethodAttributes.NewSlot,
        CallingConventions.HasThis,
        method.ReturnType,
        methodTypes);
    
    _typeBuilder.DefineMethodOverride(m, _interfaceType.GetMethod(method.Name, methodTypes));
    
    var il = m.GetILGenerator();
    var localReturnValue = il.DeclareReturnValue(method);
    
    var localCurrentMethod = il.DeclareLocal(typeof(MethodBase));
    var localParameters = il.DeclareLocal(typeof(object[]));
    
    // var currentMethod = MethodBase.GetCurrentMethod();
    il.Call(typeof(MethodBase).GetMethod(nameof(MethodBase.GetCurrentMethod)));
    il.Emit(OpCodes.Stloc, localCurrentMethod);
    
    // var baseMethod = method.BaseType.GetMethod(...);
    var localBaseMethod = il.DeclareLocal(typeof(MethodBase));
    il.Emit(OpCodes.Ldloc, localCurrentMethod);
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.GetCustomAttributeOnBaseMethod)));
    il.Emit(OpCodes.Stloc, localBaseMethod);
    
    
    // var parameters = new[] {a, b, c};
    il.Emit(OpCodes.Ldc_I4, methodTypes.Length);
    il.Emit(OpCodes.Newarr, typeof(object));
    if (methodTypes.Length > 0)
    {
        for (var i = 0; i < methodTypes.Length; i++)
        {
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldc_I4, i);
            il.Emit(OpCodes.Ldarg, i + 1);
            if (methodTypes[i].IsValueType)
            {
                il.Emit(OpCodes.Box, methodTypes[i].UnderlyingSystemType);
            }

            il.Emit(OpCodes.Stelem_Ref);
        }
    }
    il.Emit(OpCodes.Stloc, localParameters);

    // var aspectInvocation = new AspectInvocation(method, this, parameters);
    var localAspectInvocation = il.DeclareLocal(typeof(AspectInvocation));
    il.Emit(OpCodes.Ldloc, localCurrentMethod);
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldloc, localParameters);

    il.New(typeof(AspectInvocation).GetConstructors()[0]);
    il.Emit(OpCodes.Stloc, localAspectInvocation);
    
    // var classAttributes = GetType().GetOnMethodBoundAspectAttributes();
    var localClassAttributes = il.DeclareLocal(typeof(OnMethodBoundAspectAttribute[]));
    il.Emit(OpCodes.Ldarg_0);
    il.Call(_implementationType.GetMethod(nameof(GetType)));
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.GetOnMethodBoundAspectAttributes), new[] {typeof(Type)}));
    il.Emit(OpCodes.Stloc, localClassAttributes);
    
    // var methodAttributes = method.GetOnMethodBoundAspectAttributes();
    var localMethodAttributes = il.DeclareLocal(typeof(OnMethodBoundAspectAttribute[]));
    il.Emit(OpCodes.Ldloc, localBaseMethod);
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.GetOnMethodBoundAspectAttributes), new[] {typeof(MethodBase)}));
    il.Emit(OpCodes.Stloc_S, localMethodAttributes);
    
    
    // classAttributes.ForEachOnBefore(invocation);
    il.Emit(OpCodes.Ldloc, localClassAttributes);
    il.Emit(OpCodes.Ldloc, localAspectInvocation);
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.ForEachOnBefore)));
    il.Emit(OpCodes.Nop);
    
    // methodAttributes.ForEachOnBefore(invocation);
    il.Emit(OpCodes.Ldloc, localMethodAttributes);
    il.Emit(OpCodes.Ldloc, localAspectInvocation);
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.ForEachOnBefore)));
    il.Emit(OpCodes.Nop);
    
    il.LoadParameters(method);
    il.Call(_implementationType.GetMethod(method.Name, methodTypes));
    
    // methodAttributes.ForEachOnAfter(invocation);
    il.Emit(OpCodes.Ldloc, localMethodAttributes);
    il.Emit(OpCodes.Ldloc, localAspectInvocation);
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.ForEachOnAfter)));
    il.Emit(OpCodes.Nop);
    
    // classAttributes.ForEachOnAfter(invocation);
    il.Emit(OpCodes.Ldloc, localClassAttributes);
    il.Emit(OpCodes.Ldloc, localAspectInvocation);
    il.Call(typeof(OnMethodBoundAspectAttributeExtension).GetMethod(nameof(OnMethodBoundAspectAttributeExtension.ForEachOnAfter)));
    il.Emit(OpCodes.Nop);
    
    il.Return(method, localReturnValue);
}

정리

기본적인 구현과 코드로 AOP 를 구현하는 방법에 대해 알아보았다. 간단하게 AOP 를 사용하는 코드를 살펴보면 다음과 같다.

[LoggingAspect]
public class Print : IPrint
{
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

// --- Before: Void PrintMessage(System.String), 9a19fdd7e64943c9b22ae2c79a886b50, Hello World ---
// Hello World
// --- After ---

SimpleAop 코드를 보면 알겠지만, 비교적 짧은 코드로 AOP 프레임워크(?) 를 구현하였다. 이미 이보다 좋은 AOP 프레임워크가 많이 있겠지만, 직접 AOP 를 구현해 봄으로서 언어적 특성을 더 잘 알 수 있고, 언어가 제공하는 플랫폼을 이해하는 데 큰 도움이 될 것이다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

Javascript-OOP-AOP-IoC / 자바스크립트 객체지향 프로그래밍

자바스크립트 객체지향 프로그램을 쉽게 하기 위한 소스 코드를 github 에 공개(https://github.com/powerumc/Javascript-OOP-AOP-IoC)했다.

자바스크립트로 객체지향 프로그래밍을 잘 하려면 배워야 하는 것들이 참 많다. 함수형 프로그래밍과 자바스크립트의 prototype 기반의 chain, 함수를 인스턴스로 사용하고, 객체지향적인 몇 가지 자바스크립트 패턴을 익혀야 하는 데, 쉽지만은 않을 것이다. C

가장 간단한 객체지향 코드를 보자. 이 코드는 Program 클래스를 상속 받은 Outlook 클래스가 있고, run() 메서드로 실행하는 코드다.

function INHERITANCE(PARENT, CLASS) {
	for(var p in PARENT) if(PARENT.hasOwnProperty(p)) CLASS[p] = PARENT[p];

	var PROXY 					= function() { };
	PROXY.prototype 			= PARENT.prototype;
	CLASS.prototype				= new PROXY();
	CLASS.prototype.constructor = CLASS;
}

var Program = (function() {
	Program.prototype.version = "1.0.0";

	return Program;
});

var Outlook = (function() {
	INHERITANCE(Program, Outlook);
	function Outlook() {
		Program.apply(this, arguments);
	}

	Outlook.prototype.run = function() { console.log("[" + this.version + "] Running... "); }
	
	return Outlook;

})(Program);

var outlook = new Outlook();
outlook.run();

너무 성급하게 이해하지 않아도 된다.

조금이라도 쉽게 Javascript 객체지향을 하기 위해 만든 라이브러리를 공개했으니...

설치

  • npm
npm install javascript-oop-aop
  • bower
bower install javascript-oop-aop-ioc

1. 기초

이 라이브러리를 이용하면 매우 쉽게 클래스를 선언할 수 있습니다. oop.class(...) 를 사용합니다.

oop.class( [parents,] classInfo )

  • 클래스 선언
var Program = oop.class({
	say: function() { return "Hello"; }
});

var p = new Program();
p.say();

// return "Hello"
프로퍼티 선언
  • 기본적인 프로퍼티 선언
// Define class.
var Program = oop.class({
	say: function() { return "Hello"; },
	name: "엄준일"
});

var p = new Program();
console.log("My name is ", p.name);

// output
My name is 엄준일
  • 사용자 정의 get/set 프로퍼티 선언
var Program = oop.class({
	say: function() { return "Hello"; },
	name: "엄준일",
	age: { get: function()      { return this._age; },
		   set: function(value) { this._age = value; }
});

var p = new Program();
p.age = 35;
console.log("My age is ", p.age);

// output
My age is 35

2. 상속

oop.class( parents, classInfo )

  • 부모 클래스 상속하기
// Define parent class
var Program = oop.class({
	version: "1.0.2",
	show: function() { 
		console.log("openning window."); 
		/* some code.. */
	}
});

// Define class.
var Outlook = oop.class( Program, {
	run: function() { console.log("running outlook program."); }
});

// Run code.
var outlook = new Outlook();
console.log("version " + outlook.version);
outlook.run();
outlook.show();

// Output
version 1.0.2
running outlook program.
openning window.
  • 자기 자신 참고 (this or self)
var Program = oop.class({
	version: "1.0.2",
	show: function() { 
		console.log("openning window.");
		/* some code.. */ }
});

var Outlook = oop.class( Program, {
	run: function(self) { // inject 'self' argument name.
		console.log("running outlook program.");

		// *** HERE ***
		// a method call inhertianced Program.show method.
		self.show();
	}
});

var outlook = new Outlook();
console.log("version " + outlook.version);
outlook.run();
//outlook.show();      remove this line.

// Output
version 1.0.2
running outlook program.
openning window.
부모 인스턴스 참조

var Program = oop.class({
	run: function() { console.log("run Program.") }
});

var Outlook = oop.class( Program, { // HERE inheritance Program class.
	run: function(base) \ 
		console.log("run Outlook.");  

		// *** HERE ***
		// You can call parent method from base keyword.
		base.run();
	}
});

// Output
// run Outlook.
// run Program.

3. 인젝션 (주입)

oop.inject( [argument], ... )

  • 매개변수 주입
var Program = oop.class({
	version: "v1.0"
});

var Outlook = oop.class( Program, {
	version: "v2.0",
	run: function(base, self) { 
		console.log("base version: "   , base.version)
		console.log("current version: ", self.version);
	}
});

var outlook = new Outlook();
outlook.run();

// Output
base version: v1.0
current version: v2.0
  • 컨테이너로부터 주입

4. 가로채기 - AOP

oop.interception( function, behavior )

oop.interceptionBehavior( before, after, exception, finally_ )

  • 클래스 또는 메서드 가로채기

    • 메서드 가로채기
var Program = oop.class({
	run: function(msg) { console.log("run Program. ", msg); }
});

// *** HERE ***
// Setup the interception a method
var p = new Program();
oop.interception( p.run, oop.behaviors.LoggingBehavior );

// Call a 'run' method.
p.run("Now running...");

// Output
------ enter interception ------
[Thu Nov 13 2014 09:29:41 GMT+0900 (KST)]  {}
run Program.  Now running...
------ end interception ------
  • 클래스 인스턴스 가로채기
var Program = oop.class({
	run: function()       { console.log("run Program.", msg); },
	terminate: function() { console.log("Terminated the Program.") }
});

// *** HERE ***
// Pass class instance arguments
var p = new Program();
oop.interception( p, oop.behaviors.LoggingBehavior );

// Call a 'run' method.
p.run("Now running...");
p.terminate();

// Output
------ enter interception ------
[Thu Nov 13 2014 09:29:41 GMT+0900 (KST)]  {}
run Program.  Now running...
Terminated the Program.
------ end interception ------
  • 사용자 정의 가로채기 (Behaviors)

    • 사용자 정의 가로채기 정의

가로채기 행위를 사용자 정의로 만드시려면 oop.interceptionBehavior 메서드를 호출합니다.

var customBehavior = oop.interceptionBehavior(
	function() { console.log("before"); },
	function() { console.log("after"); },
	function() { console.log("throw exception"); },
	function() { console.log("finally"); }
);

var Program = oop.class({
	run: function() { console.log("run Program."); }
});

var p = new Program();
oop.interception(p,  customBehavior);
p.run();

// Output
before
run Program.
after
finally

코드가 실행 중 예외가 발생할 경우 다음 처럼 exception 함수가 실행됩니다.

var Program = oop.class({
	run: function() { 
		console.log("run Program."); 
		throw "crashing... "; 
}});

var p = new Program();
oop.interception(p,  customBehavior);
p.run();

// Output
before
run Program.
throw exception crashing...   // HERE exception behavior.
finally


Posted by 땡초 POWERUMC

댓글을 달아 주세요

반 객체지향(Half-of-OOP)

가장 먼저 명심하자. 오브젝티브-C는 C언어의 슈퍼셋(Super Set)이고 C++의 객체 지향과는 거리가 멀다. 오브젝티브-C 언어는 마치 객체지향 프로그래밍(OOP, Object-Oriented Programming) 처럼 보이지만 객체지향 언어가 아니다. 그렇다고 완전히 함수형 언어도 아니다.

하지만, 오브젝티브-C는 객체지향의 가장 대표적인 특징인 상속(Inheritence)이 가능하고 인터페이스 구현이 가능하다. ANSI-C 입장에서 바라보면 상속과 인터페이스 구현은 함수형 언어로서 가당치도 않은 언어적 특성임에 틀림 없을 것이다. (이 문장의 인터페이스는 오브젝티브-C의 @protocol을 의미함.)

물론, C 언어에서도 구조체(struct), 포인터(pointer) 등을 조합하여 객체처럼 다룰 수 있지만, 오브젝티브-C 처럼 상속과 인터페이스의 구현은 객체지향 언어가 아님에도 불구하고 완전히 객체지향 프로그래밍 처럼 개발하는데 모자람이 없을 지경이다.

정리해 보면 오브젝티브-C의 객체지향 특징은 다음과 같다.

1. 상속성(Inheritance)

상속성은 부모의 특징을 물려 받음과 동시에 재정의(override), 부모 호출(super 또는 base)를 지원한다. 더 자세히 말할 필요는 없을 것 같아서 걍~ 넘어간다.

// 오브젝티브-C 의 상속
@interface MFAppController : NSObject
{
   // .. 생략 ..
}

2. @protocol - 인터페이스 구현

오브젝티브-C의 프로토콜(protocol)은 C#과 Java의 interface와 같지만, 한 가지 다른 점은 인터페이스에 정의된 메서드 구현이 필수가 아니라는 점이다. 프로토콜의 모든 메서드를 굳이 구현하기 싫으면 하지 않아도 오브젝티브-C는 너그럽게 용서해 준다.

어떻게 이런 것이 가능할까? 지난 아티클 ‘[Objective-C] 아름다운을 추구하는 오브젝티브-C 언어 1/N’ 에서 다룬 것 처럼 메시지를 기반으로 메서드 호출을 위임하는 언어적인 특성 때문에 가능하다. 모든 프로토콜의 메서드를 정의하든 말든 컴파일러는 컴파일 할 뿐이고, 런타임에서 메시지를 통해 메서드를 호출해서 메서드가 구현되지 않았으면 예외를 발생하던가 아니면 씹던가 하게 된다. (단, @optional과 @required 참고)

// 오브젝티브-C 프로토콜 및 다중 상속
@protocol Animal

   -  (void)say;

@end

@protocol Duck <Animal, NSObject>

    - (void)swim;

@end

3. @category - 확장 메서드

카테고리(@category)는 C#에서의 확장 메서드(Extension Methods)와 똑같다. 확장 메서드는 2008년 C# 3.0에 언어적인 특징으로 포함되었다. 이와 더불어 람다 표현식(Lambda Expression), 이 두 가지는 C# 코드로 표현한 라인 수를 급격히 줄일 수 있었고, 더 섬세한 객체지향 프로그램에 집중할 수 있게 해준다.

@interface NSArray (ConvertableArray)
- (NSDictionary*)ConvertToDictionary;
@end

@implementation NSArray (ConvertableArray)
- (NSDictionary*)ConvertToDictionary
{
   // .. 실제 구현 생략 ..
   return nil;
}
@end

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       NSArray* arr = [NSArray arrayWithObjects:@"A", @"B", nil];
       NSDictionary* dic = [arr ConvertToDictionary];

       // TODO
   }
   return 0;
}

같은 코드로 C# 으로 변환하면 다음 코드와 같다.

public static class ConvertableArray
{
    public static IDictionary ConvertToDictionary(this IList list)
    {
        // .. 구현 생략 ..
        return null;
    }
}

class MainClass
{
    public static void Main (string[] args)
    {
        var arr = new List<String> { "A", "B" };
        var dic = arr.ConvertToDictionary ();
    }
}

하지만, 내부적으로 처리되는 매커니즘은 완전히 틀리다. 무엇이 그렇게도 완전히 틀릴까?

  • C# 확장 메서드는 정적 클래스의 정적 메서드로만 표현할 수 있다.
  • 오브젝티브-C 카테고리는 인스턴스 메서드이다.

그러므로 오브젝티브-C의 카테고리(@category)는 C#의 확장 메서드와 달리 인스턴스화 된 카테고리 메서드가 호출될 때 마다 참조 카운터가 증가한다. NSArray 객체가 인스턴스화 되었기 때문에 카테고리 메서드를 호출할 수 있다는 이야기가 된다.

카테고리의 메서드는 디버거를 통해 다음과 같이 역어셈블리 된다. 역어셈블리 코드를 좀 더 보기 편하기 위해 로컬 변수를 선언하여 살펴 보았다.

먼저 48만큼 메모리 사이즈를 확보한 다음 넘어온 값과 로컬 변수를 초기화하거나 값을 대입한다. rbp-8( =rdi)self 가 들어간 것이고, rbp-16 (=rsi)는 메서드 정보가 들어간다. rbp-20 (=$10)은 INT32 정수 값이 할당된다. 마지막으로 +48로 원래 스택 포인터로 돌아와 빈 값을 리턴하게 된다. 스택이 8씩 자라는 것은 필자가 소스코드를 64비트 대상으로 컴파일 했기 때문이다.

0x100000d40:  pushq  %rbp
0x100000d41:  movq   %rsp, %rbp
0x100000d44:  subq   $48, %rsp
0x100000d48:  movq   %rdi, -8(%rbp)
0x100000d4c:  movq   %rsi, -16(%rbp)
0x100000d50:  movl   $10, -20(%rbp)
0x100000d57:  movq   -8(%rbp), %rsi
0x100000d5b:  movq   %rsi, %rdi
0x100000d5e:  callq  0x100000eac               ; symbol stub for: objc_retain
0x100000d63:  leaq   -32(%rbp), %rdi
0x100000d67:  movabsq$0, %rsi
0x100000d71:  movq   %rax, -32(%rbp)
0x100000d75:  movl   $1, -36(%rbp)
0x100000d7c:  callq  0x100000eb8               ; symbol stub for: objc_storeStrong
0x100000d81:  movabsq$0, %rax
0x100000d8b:  addq   $48, %rsp
0x100000d8f:  popq   %rbp
0x100000d90:  ret    



Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. porcokang 2015.01.09 10:50 Address Modify/Delete Reply

    글을 쓰다 만듯한 포스트 입니다. 하지만 잘 읽었습니다.


필자는 최근 자바스크립트(Javascript)를 자주 만지게 되면서 몇 가지 팁 또는 가이드 정보를 공유하고자 한다. 자바스크립트(Javascript)를 좋아하지만 잘 하지는 못한다. 그래서 먼저 개념적으로 잘못된 부분이 있으면 정중하게 미리 양해를 구하고자 한다.

1. 익명의 즉시 실행 함수로 스크립트를 시작하자

익명(Anonymous)의 즉시 실행 함수(Immediately Invoked Function Expression)는 다음의 코드와 같이 정의된다.

(function() {
       // ... 코드 생략 ...  
}());  

익명 함수(Anonymous Function)는 자바스크립트(Javascript)가 런타임(Runtime)에 구문을 해석하여 실행한다. 이는 외부의 접근을 제한함을 의미한다. 그러므로 외부 코드에서 익명 함수의 내부 코드를 임의적이나 악의적으로 수정할 수 없다.

즉시 실행 함수(Immediately Invoked Function Expression)는 선언과 동시에 실행이 된다. 익명 함수가 런타임에 사용될 준비가 되면 즉시 함수의 초기화 코드를 실행할 수 있기 때문이다. 예를 들어, 아래와 같은 코드는 악의적으로 전역 변수의 영향을 받지 않도록 함수(Function) 을 보호할 수 있다.

만약 익명 함수에 정의된 undefined 매개 변수에 진짜 undefined 가 전달되기를 기대하는 경우가 있는데, 즉시 실행 함수(Immediately Invoked Function Expression) 중간에 undefined = true 에 의해 원하는 결과를 얻을 수 없게 된다.

var undefined = true;  

(function(undefined) {
   console.info(undefined);
   console.info(undefined === true);  
})(undefined)  

// 실행 결과  
true  
true  

만약, 외부 코드에 의해 undefined 가 완벽하게 변경되길 바라지 않는 경우 다음과 같이 undefined 가 반환되는 결과를 매개변수로 사용하여 외부 코드에 의한 원치 않는 결과의 충격에서 보호할 수 있게 된다.

var undefined = true;  

(function(undefined) {  

console.info(undefined);  
console.info(undefined === true);  

})( function() {}() );  

// 실행 결과  
undefined  
false  

2. 모듈화 패턴은 한 가지만 사용하라

Javascript 를 모듈화하기 위한 패턴은 여러 가지 방법이 있다. 그런데 여러 패턴을 섞어 사용하다 보면 코드를 보기가 더 난해해 진다. 때문에 필자는 private과 public 접근 제한 방법을 제공하는 모듈 패턴(Module Pattern)을 주로 사용한다. [1]

이 외에도 효과적인 패턴들이 많이 존재하므로 적당한 패턴을 선정하여 모두가 함께 같은 패턴으로 사용하는 것이 현명할 것 같다.

var umc = umc || (function() {

    var privateFunction1 = function() {
        // ... 생략 ...
        return true;
    };

    return {
      "version"   : "3.0.0.0",
      "name"      : "Umc.Core Frameworks",
      "getObject" : function() {
          var result = (privateFunction1() ? "POWERUMC" : "HTTP://BLOG.POWERUMC.KR");
          return result;
      }  
};
})();  

console.info( umc.version );    // 결과 : 3.0.0.0  
console.info( umc.name );      // 결과 : Umc.Core Frameworks  
console.info( umc.getObject() );    // 결과 : POWERUMC     
console.info( umc.privateFunction1() );    // 오류  

[코드1] Self-Contained 모듈화 패턴

var umc = umc || (function() {  

   var _version = "3.0.0.0";
   var _name = "POWERUMC";
   var _getObject = function() { /* 코드 생략 };

   return {
      "version"   : _version,
      "name"      : _name,
      "getObject" : _getObject
   }  
})();  

[코드2] Imports 모듈화 패턴

var umc = umc || (function() {  

   var module = { };

   var _version = "3.0.0.0";
   var _name = "POWERUMC";
   var _getObject = function() { /* 코드 생략 };

   module.version = _version;
   module.name = _name;
   module.getObject = _getObject;

   return module;  
})();  

[코드3] Exports 모듈화 패턴

모듈 패턴(Module Pattern)을 정의하는 방법은 몇 가지가 있다.

  1. Self-Contained
    바로 위의 예시로 제시한 코드가 바로 Self-Contained 인데, public 함수를 직접 구현하는 방법이다.

  2. Imports-mixin
    아래와 같은 코드가 Imports 하는 방법으로 정의하는 모듈 패턴(Module) 패턴이다. 코드는 return 코드 부분만 보면 된다.

    1번과 2번의 경우는 함수가 익명(Anonymous) 함수로 정의 된다.

  3. Exports
    아래와 같은 코드는 module 을 외부로 공개하여 쉽게 확장이 가능하도록 한다.

3. 함수의 매개변수는 맨 먼저 초기화 하라

자바스크립트(Javascript)는 매우 관대하다. ‘오래 꽥꽥(Duck Typing)’은 동적 언어(Dynamic Languages)의 가장 큰 매력이라고 할 수 있다. 혹시라도 강아지가 ‘꽥꽥’ 거리면 자바스크립트는 강아지를 오리로 취급한다.

정의되지 않은 변수나 매개변수(Arguments), 아래의 코드 처럼 write("ERROR"); 와 같이 매개 변수의 개수가 맞지 않아도 너그럽게 실행이 된다. 하지만 코드에 정의되지 않은 undefined에 의해 실행은 얼마든지 오동작이 가능하므로, 가능하면 매개변수는 항상 올바르게 넘겨주지 않는다는 가정하에 작성하는 것이 좋다.

만약, message = message || "-";과 같이 초기화 또는 방어 코드가 없다면 로그는 ''Sun Jun 02 2013 13:05:38 GMT+0900 (KST) : ERROR / undefined 와 같은 결과를 보여준다.

var write = function(category, message) {

    category = category || "INFO";
    message  = message  || "-";

    var date = new Date();;

    console.info( date + " : " + category + " / " + message );  
};

write("INFO", "시작 메시지");  
write("WARN", "경고 메시지");  
write("ERROR");  

// 실행 결과  
Sun Jun 02 2013 13:05:38 GMT+0900 (KST) : INFO / 시작 메시지  
Sun Jun 02 2013 13:05:38 GMT+0900 (KST) : WARN / 경고 메시지  
Sun Jun 02 2013 13:05:38 GMT+0900 (KST) : ERROR / -  

4. 매개변수가 있지만 없어도 동작하도록 해라

함수의 매개변수는 맨 먼저 초기화 하라’ 에서 본 write("ERROR"); 와 같이 매개 변수가 맞지 않아도 잘 동작하도록 방어적인 초기화 코드를 작성하였다.

하지만, write(); 의도적으로 호출을 할 경우 다음의 코드처럼 올바르게 로그 기록이 남지 않게 된다.

write("실행 종료");  

// 실행 결과  
Sun Jun 02 2013 13:14:01 GMT+0900 (KST) : 실행 종료 / -  

category 에 로그 형식이 전달되어야 하는데, 로그 메시지 ‘실행 종료’가 category 항목으로 초기화되어 엉뚱한 결과가 나오게 된다.

이런 경우는 좀 더 매개 변수가 전달되는 가능성을 좀 더 열어두어, 매개 변수의 개수에 따라 매개 변수를 직접 할당하도록 하는 아래의 코드와 같은 방법을 사용하면 된다.

var write = function(category, message) {

    category = category || "INFO";
    message  = message  || "-";

    if( arguments.length === 1 ) {
        message = category;
        category = "INFO";
    }

    var date = new Date();;
    console.info( date + " : " + category + " / " + message );
}  

write("INFO", "실행 시작");  
write("WARN", "실행 중 경고");  
write("실행 종료");  
write();  

// 실행 결과  
Sun Jun 02 2013 23:20:44 GMT+0900 (KST) : INFO / 실행 시작  
Sun Jun 02 2013 23:20:44 GMT+0900 (KST) : WARN / 실행 중 경고  
Sun Jun 02 2013 23:20:44 GMT+0900 (KST) : INFO / 실행 종료  
Sun Jun 02 2013 23:20:44 GMT+0900 (KST) : INFO / -  

5. 띄어쓰기 스타일(Intent Style)은 반드시 K&R Style 을 사용하자.

K&R 스타일은 전세계의 커뮤니티에 따라 다르지만, C, C++, C#, Java 등의 언어에서 사용된다고 한다. 참고로, 마이크로소프트(Microsoft)가 추천하는 띄어쓰기 스타일은 ‘Allman Style’ 이라고 부른다.

위키피디아(Wikipedia)에 따르면 K&R 스타일은 다음과 같은 유래가 있다고 한다.

The K&R style, so named because it was used in Kernighan and Ritchie’s book The C Programming Language, is commonly used in C. It is also used for C++, C#, and other curly brace programming languages.

K&R 스타일의 이름의 유래는 Kernighan씨와 Ritchie씨의 저술서 The C Programming Language의 C 코드에서 일반적으로 사용했다. 또한 C++, C#, 그리고 중괄호(curly brace)를 사용하는 프로그래밍 언어에서도 K&R 스타일을 사용한다.

자바스크립트(Javascript)는 K&R 스타일의 띄어쓰기 스타일을 사용한다.

필자는 자바스크립트에서 K&R 스타일을 추천하는 것이 아닌, 반드시 K&R 을 쓰는 것이 좋다고 믿는다. 다음의 코드를 보면 왜 K&R 스타일을 사용하는지 알 수 있다.

// K&R 띄어쓰기 스타일  
function getdata\_1() {  
return {
    name: "POWERUMC"  
};
}  

// Allman 뜨어쓰기 스타일  
function getdata\_2()
{  
return
{
    name: "POWERUMC";
}
}  

console.info("getdata_1() - " + getdata_1());  
console.info("getdata_2() - " + getdata_2());  

// 실행 결과  
getdata_1() - [object Object]     // 올바른 결과  
getdata_2() - undefined     // 올바르게 return 되지 않은 결과  

결론

이 외에도 몇 가지 더 있지만, 자바스크립트(Javascript) 내공을 좀 더 쌓은 후에 더 정확하게 쓰는 것이 낫겠다고 생각하여 5개의 팁 정도를 공유했다. 하지만, 예제로 제시한 코드에 대해 성능적인 면이나 익명 함수 또는 즉시 실행 함수 등의 메모리 누수 등에 대해서는 언급하지 않을 예정이다.

내용면으로 부족한 부분이 있겠지만, 이 정도의 팁과 가이드면 바로 현업에서 깔끔하고 잘 동작하는 자바스크립트(Javascript) 코드를 작성하기에는 전혀 문제가 없다고 생각한다.

디자인 패턴(Design Pattern) 부분에서 더 많은 정보를 얻길 원한다면, 필자의 언급한 ‘Learning JavaScript Design Patterns A book by Addy Osmani Volume 1.5.2’ 를 참고하기 바란다.


  1. Module Pattern 참고 : Learning JavaScript Design Patterns
    http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript  ↩


Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 2013.06.24 11:41 Address Modify/Delete Reply

    비밀댓글입니다

  2. 2015.05.22 23:56 Address Modify/Delete Reply

    비밀댓글입니다

    • 땡초 POWERUMC 2015.06.13 21:38 신고 Address Modify/Delete

      함수 안에 매개변수를 선언하는 것이 아닙니다.
      변수에 대입(assign) 하는 것이죠 ^^
      변수는 이미 함수 매개변수에 선언이 되어있으니까요.

  3. 2015.08.07 14:27 Address Modify/Delete Reply

    비밀댓글입니다

  4. 나그네 2017.09.15 13:13 Address Modify/Delete Reply

    [코드3] Exports 모듈화 패턴 에 name 오타가 있네요. 감사합니다