Category: Ruby on Rails
Yksinkertaista moniperintää Rubyn ja Pythonin mixineillä
Moniperintään liittyy useita ongelmia, joiden vuoksi esimerkiksi Java ja monet muut ohjelmointikielet eivät tue sitä. Javassa käytetään interfaceja simuloimaan moniperintää. Interfaceissa ei voi kuitenkaan olla ohjelmakoodia, joten ohjelmoijan pitää itse toteuttaa kaikki interfaceen liittyvät toiminnot omassa luokassaan. Seurauksena on sitä ikävää turhaa bloattia, josta Java-ohjelmat ovat tunnettuja.
Rubyn mixinit
Rubyssa moniperintä on ratkaistu mixineillä, jotka ovat oikein kaunis ja yksinkertainen tapa lisätä valmiita toimintoja omiin luokkiin. Tämä on kätevää pitkälti sen vuoksi, että kielessä on dynaaminen tyypitys. Mixin on käytännössä valmiita apumetodeita sisältävä moduuli. Nämä apumetodit voi lisätä "lennossa" omaan olioonsa extend-kutsulla:
module Sayable def say puts @message end end class Hello def initialize @message = 'Hello, World' end end hello = Hello.new hello.extend Sayable hello.say
Jos apumetodit haluaa lisätä pysyvästi mukaan omaan luokkaansa, sen voi tehdä luokkamäärittelyssä include-kutsulla:
class Hello def initialize @message = 'Hello, World' end include Sayable end
Pythonin mixinit
Pythonissa on moniperintä, mutta sen käyttö saattaa olla tarpeettoman hankalaa omissa projekteissa. Esimerkiksi konstruktorien kutsuminen oikealla tavalla on hieman epäintuitiivista ja menee heti rikki, jos hierarkian yksikin luokka jättää kutsumatta super-konstruktoria.
Monessa tapauksessa voikin olla järkevämpää simuloida mixinejä käyttämällä moniperintää pelkkien apumetodien määrittelyyn. Tässä lähestymistavassa mixin-luokassa ei ole konstruktoria eikä omia instanssimuutujia. Se vain olettaa, että mixiniä käyttävässä luokassa tietyt instanssimuuttujat on jo määritelty:
class Sayable(object): def say(self): print(self.message) class Hello(Sayable): def __init__(self): self.message = 'Hello, World' hello = Hello() hello.say()
Oma Hello-luokka voi periytyä jostain muusta luokasta. Silloin ohjelmoijan pitää huomioida super-konstruktorin kutsuminen oman koodinsa osalta, mutta mixin-luokista ei tarvitse välittää:
class Sayable(object): def say(self): print(self.message) class ExampleBase(object): def __init__(self): pass class Hello(ExampleBase, Sayable): def __init__(self): super(Hello, self).__init__() self.message = 'Hello, World' hello = Hello() hello.say()
Tämä malli toimii parhaiten, jos isäntäluokka on aina ensimmäisenä perimäluettelossa, ja mixin-luokat tulevat sen jälkeen. Pythonin MRO (method resolution order) kulkee nimittäin vasemmalta oikealle, ja super-kutsut kohdistuvat ensimmäisenä vasemmanpuoleisimpaan isäntäluokkaan.
Valinnan paikka: Python & Django vs. Ruby on Rails
Olen käyttänyt viime aikoina paljon energiaa sen pohtimiseen, kumpi on parempi alusta kehittää web-sovelluksia: Python-pohjainen Django vai Ruby on Rails.
Ruby on Rails
Kypsä ja elegantti, mutta kärsii suorituskyvyn rajallisuudesta. Ruby on Railsilla on vaivatonta tehdä siistejä sovelluksia, joissa kaikilla komponenteilla on vakioitu paikkansa. Toisen kehittäjän tai alihankkijan on helppo omaksua projektin rakenne. Rubyssä jopa koodin sisennys on vakioitu aina 2 spaceen.
Python & Django
"Hakkerihenkinen", helposti muokattava, erittäin suorituskykyinen. Ongelmana jokaisen projektin erilaisuus, kun sovellusten komponentteja toteutetaan eri tavoilla ja sijoitellaan sinne tänne. Ei esimerkiksi vakioitua paikkaa layout-tason templateille, omille apukirjastoille, cronjob-skripteille ja vastaaville osioille. Yrityksen pitää itse määritellä policyt näille.
Kumpi tärkeämpää, eleganttius vai suorituskyky?
Tähän se valinta kilpistyy. Pythonilla on isoja valtteja reaalimaailmassa, kuten esimerkiksi Googlen App Engine -tuki sekä hiljattain ilmestynyt FriendFeedin/Facebookin Tornado-webbipalvelin. Ruby on Rails puolestaan tuntuu olevan vähän pienempien pelurien alusta, mutta toisaalta kuitenkin Twitter käyttää sitä.Kumman sinä valitsisit?
Suorituskykyvertailua: Ruby on Rails vs. Tornado
Tämä on ehkä hieman epäortodoksinen vertailu, mutta mittailin huvikseni Facebookin hiljattain julkaiseman Tornado 0.1:n sekä myöskin uusimman Ruby on Rails 2.3.4:n suorituskykyä.
| Framework | Language | Server | Request/s |
| Tornado 0.1 (git) | Python 2.6.2 | Nginx 0.6.35 | 1961.11 |
| Ruby on Rails 2.3.4 | Ruby 1.9.1 | Apache 2.2.11/Passenger 2.2.5 | 421.94 |
| Ruby on Rails 2.3.4 | Ruby 1.9.1 | Nginx 0.7.61/Passenger 2.2.5 | 470.47 |
| Plain HTML serving | (none) | Nginx 0.7.61 | 6317.71 |
Mittaukset on tehty samalla koneella kuin tämä aiempi vertailu, mutta käyttiksenä oli nyt Ubuntu 9.04. Testiohjelmana "ab -n 100000 -c 25". Testattu sovellus oli yksinkertainen "hello world", eli tässä mitattiin lähinnä kattorajaa frameworkien suorituskyvyille. Mukana myös vertailun vuoksi staattisen HTML:n jakelu Nginxillä.
Tornado näyttäisi siis oikein suorituskykyiseltä, kun se konfiguroidaan ohjeiden mukaan. Ajoin testissä 4 rinnakkaista Tornado-prosessia (1 per CPU-core) ja niiden edessä Nginxiä. Muita kokemuksia kyseisestä frameworkista ja sen sopivuudesta web-sovelluskehitykseen ei vielä oikein ole.
Ruby on Rails ei myöskään ole ihan huonoimmasta päästä. Ruby 1.9:ssä on tiettävästi uusi tehokkaampi VM ja production-moodissa Rails pitää kaikki luokat cachetettuna muistissa. Nähdäkseni RoR yltää suurin piirtein samoihin tuloksiin kuin PHP:n nopeimmat MVC-frameworkit. Passenger puolestaan tekee RoR-sovellusten deployaamisesta varsin helppoa sekä Apachella että Nginxillä.
Pythoniin verrattuna Ruby kuitenkin vaikuttaa olevan konsistentisti aina vähintään puolet hitaampi. Djangolla sain aiemmin samalla koneella noin 1000 req/s ja Tornado vielä tuplasi sen.