Tuesday, January 23, 2007

Ruby 4

قسمت اول

قسمت دوم

قسمت سوم

Meta Programming

یکی از روش هایی که در Ruby زیاد استفاده می شود meta programming است. meta programming را بطور ساده می توان نوشتن کدی که کد دیگری را تولید می کند بیان کرد. meta programming در زبان های دیگر مانند جاوا و زبان های پلت فرم دات نت هم وجود دارد. در دات نت یک روش متداول برای این کار استفاده از Attribute ها است. یک مثال متداول استفاده از Attribute های WebMethod و WebService برای ساختن سرویس های وب هست. در جاوا تا قبل از JDK 1.5 روش متداول استفاده از XML برای این کار بود (برای مثال Axis برای تولید Stub های وب سرویس ها از XML استفاده می کرد) ولی بعد از معرفی Annotation ها(مکانیزمی شبیه Attribute ها در دات نت) در JDK 1.5 روش جایگزینی برای استفاده از XML مطرح شد. در هر حال ایده کلی برای meta programming در زبان های کامپایلری این است برنامه نویس اطلاعات اضافی را وارد کند(با استفاده از XML، Annotation، Attributes یا هر روش دیگری). سپس یک ابزار(مانند APT در جاوا) با بررسی این اطلاعات اضافی کدی را تولید کند که بتوان آنرا با استفاده از کامپایلر کامپایل کرد. روش های دیگری که برای meta programming در جاوا استفاده می شود تغیر در بایت کد کلاس ها هنگام load کردن کلاس ها، استفاده از dynamic proxy ها و تولید کلاس ها در زمان اجرا است که عمدتا روش های پیچیده ای هستند.

meta programming در Ruby بسیار ساده تر و متداول تر است. برای مثال مسئله دسترسی به فیلدهای یک شیئ را در نظر بگیرید. در جاوا راه حل متداول نوشتن متد های getter و setter و در C# استفاده از Property ها است. بطور معمول این مکانیزم ها یک کار را تکرار می کنند(getter ها مقدار فیلد را بر می گردانند و setter ها مقدار فیلد را تغییر می دهند). Ruby برای این کار راه حل جالبی دارد، در Ruby با استفاده از متدهای attr_reader و attr_writer می توان getter و setter و با استفاده از attr_accessor می توان هر دوی آنها را درست کرد. جالب این که این متد ها، متد های خاصی نیستند و تنها کاری که می کنند این است که رشته ای را درست کرده و برای اجرا به مفسر Ruby می دهند.

class FieldTest

attr_reader :r

attr_writer :w

attr_accessor :rw

def to_s

"@r, @w, @rw"

end

end

ft = FieldTest.new

ft.w = 111

ft.rw = "hello"

puts ft.r

یک راه حل ساده برای meta programming در Ruby استفاده از توابع eval است. این توابع معمولا یک رشته را به عنوان ورودی می گیرند و آن را اجرا می کند. فرض کنید می خواهیم کلاسی داشته باشیم که متد هایی با نام اعداد 1 تا 5 دارند و مقدار این اعداد را بر می گردانند، یک راه حل این است که 5 متد مختلف بنویسیم که مقادیر مورد نظر را بر می گردانند ولی یک راه حل بهتر در ادامه آورده شده:

class Numbers

nums = {'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5}

nums.each do |k, v|

module_eval "def #{k} \n #{v} \nend"

end

end

# Test class Numbers

n = Numbers.new

puts n.one # prints 1

puts n.two # prints 2

puts n.three # prints 3

puts n.four # prints 4

puts n.five # prints 5

متد module_eval رشته داده شده را به مفسر Ruby می فرستد تا آنرا اجرا کند. یک ویژگی بسیار مطلوب meta programming این است که حجم کد نوشته شده را بسیار کاهش می دهد(تصور کنید کلاس Numbers که در اینجا تنها در 4 خط نوشته شده در حالت عادی چه حجم زیادی می داشت) ولی ویژگی های مهم تری هم دارد که در ادامه به آن پرداخته ام.

Aspect Oriented Programming:

AOP یکی از روش های برنامه نویسی است که این روز ها خیلی در مورد آن صحبت می شود. AOP قابلیت های بسیاری به زبان های شیئ گرا اضافه می کند. بسیاری AOP را بعد از روش های ساخت یافته و شیئ گرا، بزرگترین تحول در دنیای برنامه نویسی دانسته اند. نکته جالب توجه این است که وقتی با Ruby کار می کنید، نیاز چندانی به AOP پیدا نمی کنید. عمده کارهایی که در AOP با آن مواجه هستید، اضافه کردن یک متد به یک کلاس، جایگزین کردن یک متد با یک متد جدید و اجرای یک کد قبل یا بعد از یک متد است. در Ruby به راحتی می توانید تمام این کارها را انجام دهید. مثلا فرض کنید بخواهید قبل از اینکه متد sort از کلاس Array فراخوانی شود، رشته ای را چاپ کنیم تا از فرا خوانی این متد باخبر شویم. کافی است کلاس Array را باز کنیم، متد sort را به old_sort تغییر نام دهیم و متد sort جدیدی را تعریف کنیم، در متد جدید کافی است رشته مورد نظر را چاپ کنیم و بعد متد قدیمی را فراخوانی کنیم:

class Array

alias_method :old_sort, :sort

def sort

puts 'Array.sort called !'

old_sort

end

end

#Test new sort

a = [3, 2, 4, 1, 4]

puts a.sort

# Output:

# Array.sort called !

# 1 2 3 4 4

البته یکی از ویژگی های زبانهایی که از AOP پشتیبانی می کنند(مانند AspectJ) پشتیبانی از wildcard ها است که چنین چیزی بطور استاندارد در Ruby پشتیبانی نمی شود.

Database:

یکی از مسائل مهمی که هر زبان برنامه نویسی باید برای حل آن فکری بکند کار کردن با پایگاه داده در آن زبان است. زبان های مختلف راهکار های متعددی برای این مسئله ارائه داده اند، در جاوا استاندارد ساده JDBC شاید اولین تلاش در این زمینه بود. بعد از آن ارائه EJB ها (با همه سر و صدایش) و سپس راه حل های مختلف بر پایه Object Relational Mapping مثل Hibernate، iBatis و Apache OJB نشان از تلاش های گسترده در این زمینه دارد. در دنیای دات نت هم کپی های این ORM ها مثل NHibernate و iBatis.NET، تکنولوژی هایی مثل ADO.NET و در نهایت ابتکار جدید ماکروسافت به نام LINQ وجود دارد. نکته مشترکی که در تمام این راه حل ها وجود دارد static بودن زبانی است که راه کار به وسیله آن پیاده سازی شده.

اما در پایگاه داده هم Ruby راه حل ساده خود را دارد. راه حل Ruby برای این کار ActiveRecord نام دارد. فرض کنید که در پایگاه داده خود جدولی به نام Students دارید که دو فیلد name و id دارد. قطعه کد زیر یک رکورد به جدول Students اضافه می کند:

ActiveRecord::Base.establish_connection(:adapter => "mysql",

:host => "127.0.0.1",

:username => "root",

:password => "",

:database => "Uni")

class Student <

ActiveRecord::Base

# Fields are fetched from database automatically

end

stu = Student.new

stu.name = 'mohsen'

stu.id = 12345

stu.save

هنگامی که کلاسی از ActiveRecord::Base ارث می برد، این کلاس بطور اتوماتیک فیلدهای پایگاه داده را به کلاس مورد نظر اضافه می کند، نکته جالب این است که ActiveRecord برای پیدا کردن فیلدها به دنبال جدولی هم نام با کلاس(ولی بصورت جمع) می گردد. این راه حل در Ruby زیاد استفاده می شود و باعث ساده شدن کار می شود. البته اگر جداول ما نام دیگری هم داشته باشند می توان ActiveRecord را برای استفاده از آن بکار برد ولی در این حالت باید اسم جدول برای آن مشخص شود.

همچنین ActiveRecord قادر است روابط یک به چند و چند به چند بین جداول را به کلاس های برنامه map کند، ولی در اینجا قصد پرداختن به جزئیات این مسئله را ندارم.

نکته ای که باید به آن توجه کرد این است که داینامیک بودن Ruby و امکان اضافه کردن فیلد به کلاس در زمان اجرا باعث شده بتوان چنین قابلیتی را پیاده سازی کرد و در زبانهای کامپایلری امکان پیاده سازی چنین راهکاری وجود ندارد.

Web:

شاید آن چیزی که باعث متداول شدن Ruby شد پلت فرم وبی بود که بر روی این زبان ایجاد شده بود. این پلت فرم Ruby on Rails نام دارد و تغیرات زیادی را در طرز تفکر توسعه دهندگان در دنیای وب ایجاد کرده. پرداختن به Ruby on Rails خود بحث مفصلی است که در این کوچک نوشته نمی گنجد!

معایب Ruby:

در پست اول در رابطه با Ruby گفتم که قصد دارم بیشتر در رابطه با مزایای این زبان صحبت کنم تا معایبش، ولی قول داده بودم حتما در قسمتی در مورد معایب این زبان هم توضیح دهم. برای همین هم این قسمت را در اینجا آوردم ولی راستش را بخواهید فعلا چیز زیادی در این رابطه در خاطرم نیست، ولی در هر حال چند نکته را می آورم.

نکته اول اینکه Ruby یک زبان اسکریپتی است و مسلما به مراتب کند تر از زبان های کامپایلری است، ولی این نکته را هم بگویم که سرعت اجرا یکی از فاکتورهای یک زبان است و بستگی زیادی به پیاده سازی آن دارد. امروزه با توجه به کاهش قیمت سخت افزار و افزایش سرعت کامپیوترها از اهمیت سرعت اجرا به شدت کاسته شده و فاکتورهای دیگری در زبان های برنامه نویسی اهمیت پیدا کرده اند(البته زبان های اسکریپتی را دست کم نگیرید و فراموش نکنید که ویکیپدیا یکی از بزرگترین سایت های اینترنت بر روی PHP کار می کند)

نکته دوم اینکه استفاده از یک زبان اسکریپتی به معنی این است که سورس کد برنامه شما در دسترس همه قرار دارد که در بسیاری از موارد قابل قبول نیست(البته می توان از تکنیک هایی مثل Obfuscation هم استفاده کرد ولی در هر حال این کار هم مشکلات خاص خود را دارد)

نکته سوم اینکه هنگامی که با یک زبان کامپایلری کار می کنید بسیاری از مشکلات احتمالی در زمان کامپایل چک می شود و در زمان اجرا با مشکلات کمتری روبرو هستید. برای پی بردن به بسیاری از مشکلات در Ruby باید برنامه خود را اجرا کنید. از این رو برنامه های نوشته شده به زبان Ruby وابستگی زیادی به Unit Testing و Code Coverage دارند.

نکته چهارم اینکه Ruby هنوز به اندازه لازم به تکامل نرسیده. هنوز انجام یک پروژه بزرگ با جاوا ریسک کمتری را تحمیل می کند. هنوز ابزار های توسعه در Ruby در اوایل کار خود قرار دارند و راه درازی تا رسیدن به آنجایی که امروزه ابزارهای زبان هایی مثل جاوا و دات نت قرار دارند باقی مانده.

کلام آخر:

راستش را بخواهید خیلی مسائل در مورد Ruby ماند که من نتوانستم در مورد آن بنویسم. فعلا نه وقت کافی برای این کار دارم و نه حال و حوصله این کار را. برای همین هم سعی کردم هر طور که شده این پست، آخرین پست من در باره این زبان باشد. حتما این موضوع را از طرز نوشته ام احساس کرده اید. در هر حال توصیه می کنم اگر به این زبان علاقه مند شده اید دست به کار شوید و مفسر Ruby و یک IDE مناسب را download کنید و شروع به کد نویسی کنید. در ابتدای کار شاید برایتان عجیب باشد که چقدر راحت می توان به این زبان کد نوشت.

مراجع:

[1] Beyond Java, By Bruce A. Tate, O'Reilly, September 2005, ISBN: 0-596-10094-9

[2] Ruby Cookbook, By Lucas Carlson, Leonard Richardson, O'Reilly, July 2006, Print ISBN-13: 978-0-59-652369-5

[3] Programming Ruby, The Pragmatic Programmers’ Guide, Second Edition, Dave Thomas

No comments: