프로그램은 작고 단순한 것에서 크고 복잡한 것으로 진화한다. 그 과정에서 코드의 재활용성을 높이고, 유지보수를 쉽게 할 수 있는 다양한 기법들이 사용된다. 그 중의 하나가 코드를 여러개의 파일로 분리하는 것이다. 이를 통해서 얻을 수 있는 효과는 아래와 같다.
- 자주 사용되는 코드를 별도의 파일로 만들어서 필요할 때마다 재활용할 수 있다.
- 코드를 개선하면 이를 사용하고 있는 모든 애플리케이션의 동작이 개선된다.
- 코드 수정 시에 필요한 로직을 빠르게 찾을 수 있다.
- 필요한 로직만을 로드해서 메모리의 낭비를 줄일 수 있다.
모듈이란
루비에서는 필요에 따라서 로드 할 수 있도록 만들어진 파일을 모듈(module)이라고 부른다. 모듈을 만들고 모듈을 로드하는 방법을 알아보자.
모듈이 없다면
우선 모듈이 없는 애플리케이션을 하나 만들어보자. 이 코드의 파일명은 main.rb 다.
def welcome() return 'Hello world' end print welcome()
위의 코드는 아무런 문제가 없는 코드다. 하지만 welcome 메소드가 자주 사용되는 것이라고 가정해보자. 이런 경우 이것이 필요한 애플리케이션마다 이 메소드를 정의해서 사용하는 것은 유지보수도 어렵고 낭비가 될 것이다. 이럴 때 모듈이 필요하다. 메소드 welcome을 모듈로 만들어보자.
모듈의 사용
새로운 파일을 만든다. 이름은 greeting.rb이다.
greeting.rb
#greeting.rb module Greeting def Greeting.welcome() return 'Hello world' end end
루비에서 모듈은 module 구문으로 시작한다. 위의 예에서 module 뒤에 따라오는 Greeting은 모듈의 이름이 된다. 모듈을 사용하는 쪽에서는 모듈의 이름을 이용해서 메소드 Greeting.welecom와 같은 모듈의 메소드를 사용할 수 있다.
main.rb의 내용을 다음과 같이 변경한다.
main.rb
require './greeting' puts Greeting.welcome()
이전 예제와 비교했을 때 결과는 같다. 하지만 메소드 welcome을 main.rb의 외부 파일로 분리했다. 다음은 위의 코드에 대한 분석이다.
require './greeting'
require는 모듈을 로드할 때 사용하는 명령이다. greeting은 불러오려고 하는 모듈의 이름인데, 모듈의 이름은 확장자를 제외한 파일명을 사용한다. require './greeting'은 main.rb와 같은 디렉토리에 있는 greeting.rb라는 이름의 파일을 찾아서 로드한다.
Greeting.welcome
Greeting은 모듈의 이름이다. 이것은 파일의 이름이 아니다. greeting.rb 파일 내의 모듈 선언부인 module Greeting의 Greeting이 이 값이다. 즉 메소드 welcome 은 로드(require) 후에 바로 사용할 수 있는 것이 아니라 Greeting이라는 모듈의 이름을 통해서 사용할 수 있게 되는 것이다. 이러한 개념을 네임스페이스(namespace)라고 한다.
네임스페이스
네임스페이스가 무엇인가를 정의하기에 앞서서 파일을 생각해보자. 파일은 데이터를 보관하고 있는 일종의 컨테이너다. 그리고 이 컨텐이너는 파일명으로 식별이 된다. 파일의 수가 많아지면서 파일을 관리하는 것이 점점 어려워진다. 그래서 고안된 것이 바로 디렉토리다. 디렉토리를 이용하면 같은 이름의 파일이 하나의 컴퓨터에 존재할 수 있다. 파일명의 충돌을 회피 할 수 있게 된 것이다. 네임스페이스란 간단하게 디렉토리와 같은 것이라고 생각하자. 하나의 에플리케이션에는 다양한 모듈을 사용하게 된다. 그런데 모듈이 서로 다른 개발자에 의해서 만들어지기 때문에 같은 이름을 쓰는 경우가 생길 수 있다. 이런 경우 먼저 로드된 모듈은 나중에 로드된 모듈에 의해서 덮어쓰기 되기 때문에 이에 대한 대책이 필요하다. 네임스페이스가 필요해지게 되는 것이다.
만약 환영인사를 언어별로 다르게 하고 싶다면 어떻게 해야 할까? 3개의 파일을 만든다.
greeting_en.rb
module Greeting_en def welcome return 'Hello world' end end
greeting_ko.rb
module Greeting_ko def welcome return '안녕 세계' end end
main.rb
require './greeting_en' require './greeting_ko' puts welcome()
위의 코드는 무언가 이상하다. 메소드 welcome은 greeting_en.rb에도 있고 greeting_ko.rb에도 있다. 위의 코드에서 호출하고 있는 welcome이 누구의 welcome인지 불분명하다. 실행시켜보면 실제로 에러가 발생한다. 아래와 같이하면 welcome이 어디에 속한 메소드인지 분명해진다.
main.rb
require './greeting_en' require './greeting_ko' puts Greeting_en.welcome() puts Greeting_ko.welcome()
여기서 Greeting_en, Greeting_ko가 네임스페이스다. Geeting_en과 Greeting_ko라는 네임스페이스가 있기 때문에 같은 이름인 메소드 welcome이 어떤 메소드인지를 분명하게 명시할 수 있다.
모듈 기본 디렉토리 지정
지금까지 require를 이용해서 모듈을 로드하는 방법을 알아봤다. 만약 어떤 모듈이 다양한 에플리케이션에서 공통적으로 사용된다면 어떻게 해야할까? 각각의 애플리케이션 디렉토리에 모듈을 포함시키면 된다. 하지만 이렇게 하면 모듈의 내용에 변화가 생겼을 때 모든 모듈의 내용을 업데이트해야 하는 불편함이 있다. 이러한 문제를 해결하려면 어떻게 해야할까? 모듈 기본 디렉토리를 사용하면 된다. 이것의 사용법을 알아보자.
아래의 루비의 명령을 콘솔에서 실행해보자.
puts $:
실행결과는 아래와 같다.
puts $:
$:는 특수한 배열이다. 이 배열 안에는 require나 load 메소드를 이용해서 로딩하려는 파일을 찾는 디렉토리가 저장되어 있다. 이 배열을 통해서 모듈 기본 디렉토리의 목록과 우선순위를 알 수 있다. 아래는 위의 결과를 보기 좋게 정리해본 것이다.
- /usr/local/lib/site_ruby/1.9.1
- /usr/local/lib/site_ruby/1.9.1/x86_64-linux
- /usr/local/lib/site_ruby
- /usr/lib/ruby/vendor_ruby/1.9.1
- /usr/lib/ruby/vendor_ruby/1.9.1/x86_64-linux
- /usr/lib/ruby/vendor_ruby
- /usr/lib/ruby/1.9.1
- /usr/lib/ruby/1.9.1/x86_64-linux
예를들면 greeting.rb라는 모듈을 로드 할 때 루비는 위의 목록 중 제일 위에 있는 /usr/local/lib/site_ruby/1.9.1/greeting.rb 파일을 찾는다. 이 파일이 존재하지 않는다면 이번엔 /usr/local/lib/site_ruby/1.9.1/x86_64-linux/greeting.rb 파일을 찾는다. 이 파일이 존재한다면 로드하고, 존재하지 않는다면 순차적으로 파일의 존재 여부를 확인하게 되는 것이다. 위에서 열거된 디렉토리에 루비의 모듈을 위치시키면 모든 루비 애플리케이션에서 사용할 수 있게 되는 것이다.
위의 목록에서 site_ruby 디렉토리는 주로 자신이 만든 모듈을 위치시킨다. vender_ruby는 루비의 배포판(MRI, jruby)의 필요에 따른 모듈이 위치한다. 루비 버전에 종속적인 모듈은 해당 버전(여기선 1.9.1)의 디렉토리에 위치시키고, 운영체제에 종속적인 모듈(여기선 x86_64-linux)은 해당 운영체제에 알맞는 디렉토리에 위치시킨다.
모듈 로드의 우선순위
모듈을 1번 /usr/local/lib/site_ruby/1.9.1과 2번 /usr/local/lib/site_ruby/1.9.1/x86_64-linux 에 위치시켜보자.
/usr/local/lib/site_ruby/1.9.1/greeting.rb
module Greeting def Greeting.welcome() return 'hello' end end
/usr/local/lib/site_ruby/1.9.1/x86_64-linux/greeting.rb
module Greeting def Greeting.welcome() return 'world' end end
main.rb
require 'greeting' Greeting.welcome()
main.rb를 실행한 결과는 hello가 출력된다. 첫 번째 파일인 /usr/local/lib/site_ruby/1.9.1/greeting.rb 파일을 삭제 한 후에 다시 시도해보자. world가 출력될 것이다. 첫 번째 파일이 사라졌기 때문에 루비는 두 번째 파일인 /usr/local/lib/site_ruby/1.9.1/x86_64-linux/greeting.rb 모듈을 사용한 것이다.