Monet ihmiset opettelevat Rubya aloittamalla Rails-kehyksestä, ja valitettavasti tämä on huonoin mahdollinen tapa oppia tätä kieltä. Älä ymmärrä minua väärin: Se auttaa sinua rakentamaan verkkosovelluksia nopeasti ja tehokkaasti ilman, että sinun tarvitsee perehtyä moniin teknisiin yksityiskohtiin.
Hauska tavata!
Monet ihmiset opettelevat Rubya aloittamalla Rails-kehyksestä, ja valitettavasti tämä on huonoin mahdollinen tapa oppia tätä kieltä. Älä ymmärrä minua väärin: Se auttaa sinua rakentamaan verkkosovelluksia nopeasti ja tehokkaasti ilman, että sinun tarvitsee perehtyä moniin teknisiin yksityiskohtiin. Ne tarjoavat paljon "Rails-taikuutta", joka saa asiat yksinkertaisesti toimimaan. Ja aloittelevalle ohjelmoijalle tämä on todella hienoa, koska prosessin miellyttävin hetki on se, kun voit sanoa "se elää!" ja nähdä, että kaikki osat sopivat yhteen ja ihmiset käyttävät sovellustasi. Haluamme olla "tekijöitä" 🙂 Mutta on yksi asia, joka erottaa hyvät ohjelmoijat keskiverto-ohjelmoijista: hyvät ohjelmoijat ymmärtävät, miten heidän käyttämänsä työkalut toimivat. Ja "työkalujen ymmärtämisellä" en tarkoita kaikkien kehyksen tarjoamien metodien ja moduulien tuntemista, vaan sen ymmärtämistä, miten se toimii, miten "Rails-taika" tapahtuu. Vasta sitten voit tuntea olosi mukavaksi käyttää objekteja ja ohjelmoida Railsilla. Oliopohjaisen ohjelmoinnin perusta ja salainen ase, joka tekee monimutkaisesta Rails-sovelluksesta helpomman, on jo otsikossa mainittu PORO eli Plain Old Ruby Object.
Mitä tämän nimen takana todella piilee? Mikä on tämä suuri salainen ase? Se on yksinkertainen Ruby-luokka, joka ei periydy mistään. Kyllä, juuri sitä, ja niin paljon.
luokka AwesomePoro
end
Miten voin auttaa sinua?
Kehität sovellustasi jatkuvasti ja lisäät siihen uusia toimintoja, koska käyttäjien määrä ja heidän odotuksensa kasvavat. Päädyt pisteeseen, jossa törmäät yhä useampiin pimeisiin paikkoihin, joissa logiikka on äärimmäisen kieroutunutta, paikkoihin, joita rohkeimmatkin kehittäjät välttelevät kuin ruttoa. Mitä enemmän tällaisia paikkoja on, sitä vaikeampi on hallita ja kehittää sovellusta. Tavallinen esimerkki on uuden käyttäjän rekisteröinti, joka käynnistää kokonaisen joukon muita tähän tapahtumaan liittyviä toimintoja:
- IP-osoitteen tarkistaminen roskapostitietokannasta,
- lähettää sähköpostia uudelle käyttäjälle,
- bonuksen lisääminen suosittelevan käyttäjän tilille,
- luoda tilejä asiaan liittyvissä palveluissa,
- ja monia muita...
Näyte koodi joka vastaa käyttäjän rekisteröinnistä, voi näyttää tältä:
class RegistrationController < ApplicationController < ApplicationController
def create
user = User.new(registration_params)
if user.valid? && ip_valid?(registration_ip)
user.save!
user.add_bonuses
user.synkronize_related_accounts
user.send_email
end
end
end
Okei, olet koodannut sen, kaikki toimii, mutta... onko kaikki tämä koodi todella kunnossa? Ehkä voisimme kirjoittaa sen paremmin? Ensinnäkin se rikkoo ohjelmoinnin perusperiaatetta - yksittäistä vastuuta, joten voisimme varmasti kirjoittaa sen paremmin. Mutta miten? Tässä kohtaa jo mainittu PORO tulee avuksesi. Riittää, että erotamme luokan RegistrationService, joka vastaa vain yhdestä asiasta: kaikkien siihen liittyvien palveluiden ilmoittamisesta. Palveluilla tarkoitamme yksittäisiä toimia, jotka olemme jo edellä erottaneet. Samassa kontrollerissa sinun tarvitsee vain luoda objekti RegistrationService ja kutsua siihen "fire!"-metodia. Koodista on tullut paljon selkeämpää, kontrollerimme vie vähemmän tilaa, ja kukin äskettäin luoduista luokista vastaa nyt vain yhdestä toiminnosta, joten voimme tarvittaessa korvata ne helposti.
luokka RegistrationService
def fire!(params)
user = User.new(params)
if user.valid? && ip_validator.valid?(registration_ip)
user.save!
after_registered_events(user)
end
user
end
private
def after_registered_events(user)
BonusesCreator.new.fire!(user)
AccountsSynchronizator.fire!(user)
EmailSender.fire!(user)
end
def ip_validator
@ip_validator ||= IpValidator.new
end
end
class RegistrationController < ApplicationController
def create
user = RegistrationService.new.fire!(registration_params)
end
end
Plain Old Ruby Object voi kuitenkin osoittautua hyödylliseksi muissakin kuin ohjaimissa. Kuvittele, että luomasi sovellus käyttää kuukausilaskutusjärjestelmää. Tarkka päivä, jolloin tällainen laskutus luodaan, ei ole meille tärkeä, meidän on vain tiedettävä, että se koskee tiettyä kuukautta ja vuotta. Voit tietysti asettaa päivän kunkin kuukauden ensimmäiseksi päiväksi ja tallentaa tämän tiedon "Date"-luokan objektiin, mutta se ei ole todellinen tieto, etkä tarvitse sitä sovelluksessasi. Käyttämällä POROa voit luoda luokan "MonthOfYear", jonka objektit tallentavat juuri tarvitsemasi tiedot. Lisäksi, kun sovellat siihen moduulia "Comparable", voit iteroida ja vertailla sen objekteja, aivan kuten Date-luokkaa käyttäessäsi.
luokka MonthOfYear
include Comparable
attr_reader :year, :month
def initialize(kuukausi, vuosi)
raise ArgumentError ellei month.between?(1, 12)
@vuosi, @kuukausi = vuosi, kuukausi
end
def (other)
[vuosi, kuukausi] [muu.vuosi, muu.kuukausi]
end
end
Esittele minulle Rails.
Rails-maailmassa olemme tottuneet siihen, että jokainen luokka on malli, näkymä tai ohjain. Niillä on myös tarkka sijaintinsa hakemistorakenteessa, joten minne voimme sijoittaa pienen PORO-armeijamme? Harkitse muutamia vaihtoehtoja. Ensimmäinen mieleen tuleva ajatus on: jos luodut luokat eivät ole malleja, näkymiä tai ohjaimia, meidän pitäisi laittaa ne kaikki hakemistoon "/lib". Teoriassa se on hyvä ajatus, mutta jos kaikki PORO-tiedostot päätyvät yhteen hakemistoon ja sovelluksesta tulee suuri, tästä hakemistosta tulee nopeasti pimeä paikka, jota pelkäät avata. Siksi se ei epäilemättä ole hyvä idea.
AwesomeProject
├──-sovellus
│ ├─ohjaimet
│ ├─mallit
│ └─views
│
└─lib
└─palvelut
#all poro täällä
Voit myös nimetä joitakin luokkia, jotka eivät ole ActiveRecord-malleja, ja sijoittaa ne hakemistoon "app/models", ja nimetä muiden luokkien käsittelystä vastaavat luokat palveluiksi ja sijoittaa ne hakemistoon "app/services". Tämä on melko hyvä ratkaisu, mutta siinä on yksi haittapuoli: kun luot uuden PORO:n, sinun on joka kerta päätettävä, onko se enemmän malli vai palvelu. Näin saatat päästä tilanteeseen, jossa sovelluksessasi on kaksi pimeää paikkaa, vain pienempiä. On vielä kolmaskin lähestymistapa, nimittäin: nimitettyjen luokkien ja moduulien käyttö. Sinun tarvitsee vain luoda hakemisto, jolla on sama nimi kuin ohjaimella tai mallilla, ja sijoittaa kaikki kyseisen ohjaimen tai mallin käyttämät PORO-tiedostot sinne.
AwesomeProject
├──-sovellus
│ ├─ohjaimet
│ │ ├─ rekisteröinnin_valvoja
│ │ │ │ └─registration_service.rb
│ │ │ └─registration_controller.rb
│ ├─models
│ │ │ ├─settlement
│ │ │ │ └─month_of_year.rb
│ │ │ └─settlement.rb
│ └─views
│
└─lib
Tämän järjestelyn ansiosta sitä käytettäessä sinun ei tarvitse edeltää luokan nimeä nimiavaruudella. Olet saanut lyhyempää koodia ja loogisemman hakemistorakenteen.
Tsekkaa minut!
On miellyttävä yllätys, että kun käytät POROa, sovelluksesi yksikkötestit ovat nopeampia ja helpompia kirjoittaa, ja myöhemmin muut ymmärtävät ne todennäköisemmin. Koska jokainen luokka vastaa nyt vain yhdestä asiasta, voit tunnistaa reunaehdot nopeammin ja lisätä niihin helposti sopivia testiskenaarioita.
describe MonthOfYear do
subject { MonthOfYear.new(11, 2015) }
it { should be_kind_of Comparable }
describe "uuden instanssin luominen" do
it "alustetaan oikealla vuodella ja kuukaudella" do
expect { described_class.new(10, 2015) }.to_not raise_error_error
end
it "herättää virheen, kun annettu kuukausi on väärä" do
expect { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
end
end
end
Toivottavasti tapaamme vielä!
Esittämämme esimerkit osoittavat selvästi, että PORO:n käyttö parantaa sovellusten luettavuutta ja tekee niistä modulaarisempia, jolloin niitä on helpompi hallita ja laajentaa. Yhden vastuun periaatteen omaksuminen helpottaa tiettyjen luokkien vaihtamista tarvittaessa ja ilman, että se häiritsee muita elementtejä. Se tekee myös niiden testaamisesta yksinkertaisempaa ja nopeampaa. Lisäksi tällä tavoin Rails-mallien ja -kontrollerien pitäminen lyhyinä on paljon helpompaa, ja me kaikki tiedämme, että niillä on taipumus kasvaa tarpeettoman suuriksi kehitysprosessin aikana.