# **Ruby 최적화 및 메타프로그래밍** Ruby는 강력한 동적 언어로서 **코드를 간결하게 유지하면서도 유연한 프로그래밍이 가능**하다. 하지만 동적 특성으로 인해 성능이 저하될 수 있으며, 이를 해결하기 위한 최적화 기법이 필요하다. 또한, Ruby는 **메타프로그래밍**을 통해 코드를 동적으로 생성하고 수정할 수 있는 강력한 기능을 제공한다. 이 글에서는 **Ruby 성능 최적화 방법**과 **메타프로그래밍 활용법**을 정리한다. --- ## **1. Ruby 성능 최적화** Ruby는 생산성이 높은 언어지만 속도는 C나 Java보다 느릴 수 있다. 따라서 Ruby 코드의 성능을 최적화하는 것이 중요하다. ### **1.1. 불필요한 객체 생성을 줄이기** Ruby에서는 **모든 것이 객체**이므로, 불필요한 객체 생성을 피하는 것이 중요하다. #### **(1) 문자열 객체 재사용** `String` 객체를 반복 생성하면 메모리를 낭비할 수 있다. **비효율적인 코드:** ```ruby 100_000.times do str = "Hello" # 새로운 String 객체 생성 end ``` **효율적인 코드:** ```ruby 100_000.times do str = "Hello".freeze # 동일 객체 재사용 end ``` `.freeze`를 사용하면 객체가 변경되지 않도록 고정되어 **메모리 사용량을 줄일 수 있다.** --- ### **1.2. `each` 대신 `map`과 `reduce` 활용** 반복문(`each`)을 사용하면 불필요한 루프가 발생할 수 있다. `map`과 `reduce`를 사용하면 코드의 가독성이 향상되고 성능이 개선된다. **비효율적인 코드:** ```ruby result = [] [1, 2, 3, 4, 5].each do |num| result << num * 2 end ``` **효율적인 코드:** ```ruby result = [1, 2, 3, 4, 5].map { |num| num * 2 } ``` --- ### **1.3. `symbol`과 `hash` 활용** Ruby에서 `Symbol`은 `String`보다 가볍고 성능이 좋다. ```ruby # String 기반 키 사용 (비효율적) hash = { "name" => "Alice", "age" => 25 } # Symbol 기반 키 사용 (효율적) hash = { name: "Alice", age: 25 } ``` **`Symbol`은 불변 객체이므로, 동일한 값이 여러 번 생성되지 않아 성능이 향상된다.** --- ### **1.4. `lazy`를 활용한 지연 평가** `lazy`를 사용하면 **모든 데이터를 한 번에 로드하지 않고, 필요한 만큼만 계산**할 수 있다. ```ruby numbers = (1..Float::INFINITY).lazy.map { |n| n * 2 } puts numbers.first(5) # [2, 4, 6, 8, 10]만 생성됨 ``` --- ### **1.5. 메모이제이션(Memoization) 활용** 반복적으로 계산해야 하는 값은 **한 번만 계산하고 저장**하는 것이 좋다. ```ruby def slow_method @result ||= expensive_computation end def expensive_computation sleep(2) "Computed!" end puts slow_method # 첫 실행은 2초 대기 puts slow_method # 두 번째 실행은 즉시 반환 ``` `@result ||= expensive_computation`을 사용하면 **한 번만 계산하고 이후에는 캐시된 값을 반환**한다. --- ## **2. 메타프로그래밍** 메타프로그래밍이란 **코드를 실행 중에 변경하거나 새로운 메서드를 동적으로 정의하는 기법**이다. --- ### **2.1. `define_method`를 활용한 동적 메서드 생성** `define_method`를 사용하면 **반복적인 메서드 정의를 줄이고 코드의 유연성을 높일 수 있다.** ```ruby class User [:name, :age, :email].each do |attribute| define_method(attribute) do instance_variable_get("@#{attribute}") end define_method("#{attribute}=") do |value| instance_variable_set("@#{attribute}", value) end end end user = User.new user.name = "Alice" puts user.name # Alice ``` 이렇게 하면 **setter/getter 메서드를 자동으로 생성**할 수 있다. --- ### **2.2. `method_missing`을 활용한 동적 메서드 처리** 정의되지 않은 메서드가 호출될 때 `method_missing`을 활용하면 유연한 처리가 가능하다. ```ruby class DynamicMethod def method_missing(name, *args) puts "#{name} 메서드가 호출됨 (args: #{args})" end end obj = DynamicMethod.new obj.hello(1, 2, 3) # hello 메서드가 호출됨 (args: [1, 2, 3]) ``` `method_missing`을 이용하면 **일일이 메서드를 정의하지 않고도 유연하게 동작을 처리할 수 있다.** --- ### **2.3. `class_eval`과 `instance_eval`을 활용한 클래스 동적 수정** ```ruby class Person end Person.class_eval do def greet "Hello!" end end puts Person.new.greet # Hello! ``` `class_eval`을 사용하면 **기존 클래스에 동적으로 메서드를 추가할 수 있다.** --- ### **2.4. `self.included`을 활용한 모듈 자동 확장** ```ruby module AutoMethods def self.included(base) base.extend ClassMethods end module ClassMethods def say_hello "Hello from #{self}" end end end class MyClass include AutoMethods end puts MyClass.say_hello # Hello from MyClass ``` 모듈을 `include`하면 자동으로 클래스 메서드가 추가되도록 설정할 수 있다. --- ## **3. 결론** Ruby는 **동적인 특성**을 활용하여 성능 최적화와 메타프로그래밍이 가능하다. | 주제 | 주요 내용 | |------|---------------------------------| | **최적화** | 객체 생성을 줄이고, `map`, `symbol`, `lazy` 등을 활용 | | **메타프로그래밍** | `define_method`, `method_missing`, `class_eval`을 활용한 동적 코드 작성 | **최적화를 통해 성능을 개선하고, 메타프로그래밍을 활용하여 유지보수가 쉬운 코드를 작성하는 것이 핵심이다.**