no Moose; or use namespace::clean or use namespace::autoclean ?

| | Comments (0) | TrackBacks (0)
요즘 Perl 5의 새로운 OOP프레임웍으로 뜨고있는 Moose로 클래스를 정의할 때 각종 문서나 책마다 조금씩은 다른 방법으로 사용하는 부분이 있는데 그것은 네임스페이스를 청소하는 방법이다.

package SomeClass;
use Moose;
 .
 .
 .
__PACKAGE__->meta->make_immutable;
no Moose;
1;




package SomeClass;
use Moose;
use namespace::clean -except => 'meta';
 .
 .
 .
__PACKAGE__->meta->make_immutable;
1;




package SomeClass;
use Moose;
use namespace::autoclean;
 .
 .
 .
__PACKAGE__->meta->make_immutable;
1;

여기서 네임스페이스를 청소한다는 말은 use Moose 하면서 import된 클래스/객체를 구성하기 위해 사용되는 Sugar함수들(has,after,extends등)을 사용이 다 끝나면 제거한다는 말이다.(meta 관련 함수는 meta object protocol 구현과 관련있기 때문에 제거하지 않는다.) 제거를 하지 않아도 문제가 없을 수 있겠지만 어떠한 함수들이 자동으로 import되었는지 알기 어렵고 나중에 해당 클래스를 사용하는 코드에서 그러한 함수들이 예기치 않은 문제를 일으킬 소지가 있기 때문에 제거하는 것이 좋다. ( __PACKAGE__->meta->make_immutable; 은 더이상 meta object protocol을 통한 동적인 변화가 없다는 걸 명시하는 것으로 처리속도를 높이기 위해 넣어주는 부분 )

그럼 위의 3가지가 어떻게 다른지 한 번 살펴보자.

여기서 제일 먼저 이해하고 넘어가야 할 것은 namespace::clean 의 동작방식이다. namespace::clean의 동작방식은 Avoid accidently creating methods from module exports( http://www.effectiveperlprogramming.com/blog/124 )와 namespace::clean 모듈문서( http://search.cpan.org/perldoc?namespace::clean )를 참고

간단하게 말하자면 namespace::clean은 use namespace::clean 이전에 정의되었거나 import된 함수(function)들을 use namespace::clean이 정의된 렉시컬 영역이 끝나는 시점에 제거하며( 여기서 주의할 것은 제거하는 시점은 compile time이 아니라는 것 )  no namespace::clean 이 나오면 제거 대상들을 use namespace::clean 다시 나올 때 까지 다시 모은다는 것이다. 그리고 제거에서 제외할 대상을 -except를 사용하여 지정할 수 있다.

$ perl -e '{ sub hello { print "hello\n" } } hello()'
hello
$ perl -e '{ sub hello { print "hello\n" } use namespace::clean; } hello()'
Undefined subroutine &main::hello called at -e line 1.
$ perl -e '{ sub hello { print "hello\n" } use namespace::clean -except=>"hello"; } hello()'
hello
$ perl -e '{ sub hello { print "hello\n" } use namespace::clean; sub say_hello { hello() } } say_hello(); hello()'
hello
Undefined subroutine &main::hello called at -e line 1.
$ perl -e '{ use namespace::clean; sub hello2 { print "hello2\n" } no namespace::clean; sub hello3 { print "hello3\n" } use namespace::clean; } hello2(); hello3()'
hello2
Undefined subroutine &main::hello3 called at -e line 1.

위 결과를 보면 이해가 갈 것이다.

그러면 이제 Avoid accidently creating methods from module exports 에 있는 현재 package에서 정의된 함수들을 보여주는 show_defined_subs 함수가 포함된 Moose 클래스를 처음에 얘기한 3가지 경우와 그것들은 사용하지 않았을 경우를 만들어서 비교해보자.

<아무것도 사용하지 않을 경우>
#!/usr/bin/env perl
use strict;
use warnings;

{
    package SomeClass;
    use Moose;
    use Carp 'croak';

    sub get_defined_subs
    {
        my( $package ) = @_;
        no strict 'refs';
        foreach my $name ( keys %{"${package}::"} )
        {
            next unless defined &{"${package}::$name"};
            print "$name\n";
        }
    }
    __PACKAGE__->meta->make_immutable;
}

SomeClass->get_defined_subs;

<결과>
around
has
blessed
meta
after
augment
inner
new
extends
get_defined_subs
before
super
DESTROY
croak
confess
override
with


<no Moose를 사용한 경우>
#!/usr/bin/env perl
use strict;
use warnings;

{
    package SomeClass;
    use Moose;
    use Carp 'croak';

    sub get_defined_subs
    {
        my( $package ) = @_;
        no strict 'refs';
        foreach my $name ( keys %{"${package}::"} )
        {
            next unless defined &{"${package}::$name"};
            print "$name\n";
        }
    }
    __PACKAGE__->meta->make_immutable;
    no Moose;
}

SomeClass->get_defined_subs;

<결과>
meta
new
get_defined_subs
DESTROY
croak


<use namespace::clean -except => 'meta' 를 사용한 경우>
#!/usr/bin/env perl
use strict;
use warnings;

{
    package SomeClass;
    use Moose;
    use Carp 'croak';
    use namespace::clean -except => 'meta';

    sub get_defined_subs
    {
        my( $package ) = @_;
        no strict 'refs';
        foreach my $name ( keys %{"${package}::"} )
        {
            next unless defined &{"${package}::$name"};
            print "$name\n";
        }
    }
    __PACKAGE__->meta->make_immutable;
}

SomeClass->get_defined_subs;

<결과>
meta
new
get_defined_subs
DESTROY


<use namespace::autoclean 을 사용한 경우>
#!/usr/bin/env perl
use strict;
use warnings;

{
    package SomeClass;
    use Moose;
    use Carp 'croak';
    use namespace::autoclean;

    sub get_defined_subs
    {
        my( $package ) = @_;
        no strict 'refs';
        foreach my $name ( keys %{"${package}::"} )
        {
            next unless defined &{"${package}::$name"};
            print "$name\n";
        }
    }
    __PACKAGE__->meta->make_immutable;
}

SomeClass->get_defined_subs;

<결과>
meta
new
get_defined_subs
DESTROY

위 결과를 종합해 보면 3가지 모두 Moose와 관련된 Sugar함수들을 제거 하기는 하나 no Moose의 경우 다른 모듈을 통해서 import한 함수(위에서는 Carp의 croak함수)들은 제대로 제거하지 못하며 use namespace::clean -except => 'meta'; 나 use namespace::autoclean; 은 meta관련 함수들 빼고는 모두 똑같이 제거함을 볼 수 있다.( 다르게 보면 namespace::autoclean 은 namespace::clean의 Moose특화 버젼으로 볼 수 있음 )

그러면 Moose로 클래스를 만들때 어떤 방법을 쓰는 게 좋을까? 일단 no Moose는 위에서 보듯 어떤 경우에 완벽하지 못함을 볼 수 있다. 따라서 use namespace::autoclean;을 쓰는 것이 좋아 보인다. 그러면 use namespace::clean -except => 'meta';은 쓸데없이 길기만 한 방법일까?

하지만 꼭 그런건 아니다.  Avoid accidently creating methods from module exports 에서도 보듯이 클래스 메소드가 아닌 클래스 내부에서만 접근가능한 함수를 정의할 필요가 있을때 namespace::autoclean 모듈은 namespace::clean 의 no namespace::clean 처럼 unimport 기능을 통해 나중에 네임스페이스에서 제거할 함수를 따로 지정할 방법이 없기 때문에 경우에 따라서는 namespace::clean으로만 가능한 경우도 있게 된다.

결론을 내리자면 Moose로 클래스를 정의할때 namespace::autoclean을 쓰면 되겠고 위에서 말한 부득이한 경우에만 namespace::clean을 사용하면 된다. 둘다 나름 존재의 이유가 있다고 볼 수 있는 것이다.