Paljud inimesed õpivad Ruby keelt, alustades Railsi raamistikust, ja kahjuks on see kõige halvem võimalik viis selle keele õppimiseks. Ärge mõistke mind valesti: Rails on suurepärane, see aitab teil ehitada veebirakendusi kiiresti ja tõhusalt, ilma et peaksite süvenema paljudesse tehnilistesse üksikasjadesse.
Tore kohtuda!
Paljud inimesed õpivad Ruby keelt, alustades Railsi raamistikust, ja kahjuks on see kõige halvem võimalik viis selle keele õppimiseks. Ärge mõistke mind valesti: Rails on suurepärane, see aitab teil ehitada veebirakendusi kiiresti ja tõhusalt, ilma et peaksite süvenema paljudesse tehnilistesse üksikasjadesse. Nad pakuvad palju "Rails'i maagiat", mis paneb asjad lihtsalt tööle. Ja algaja programmeerija jaoks on see tõesti suurepärane, sest kõige meeldivam hetk on see, kui sa saad öelda "see on elus!" ja näha, et kõik osad sobivad kokku ja inimesed kasutavad su rakendust. Meile meeldib olla "loojad" 🙂 Aga on üks asi, mis eristab häid programmeerijaid keskmisest: head programmeerijad saavad aru, kuidas nende kasutatavad tööriistad töötavad. Ja "oma tööriistade mõistmise" all ei pea ma silmas kõigi raamistiku poolt pakutavate meetodite ja moodulite tundmist, vaid arusaamist, kuidas see töötab, arusaamist, kuidas "Rails'i maagia" toimub. Alles siis saab end mugavalt tunda objektide kasutamisel ja Railsiga programmeerimisel. Objektipõhise programmeerimise vundament ja salarelv, mis teeb keerulise Railsi rakenduse lihtsamaks, on juba pealkirjas mainitud PORO ehk Plain Old Ruby Object
Mis peitub tegelikult selle nime taga? Mis on see suur salarelv? See on lihtne Ruby klass, mis ei päri millestki. Jah, just seda ja nii palju.
klass AwesomePoro
end
Kuidas ma saan teid aidata?
Te arendate pidevalt oma rakendust ja lisate uusi funktsioone, kuna kasutajate arv ja nende ootused kasvavad. Sa jõuad punkti, kus puutud kokku üha enam ja enam äärmiselt keerulise loogika tumedate kohtadega, kohtadega, mida isegi kõige julgemad arendajad väldivad nagu katku. Mida rohkem on selliseid kohti, seda raskem on rakendust hallata ja arendada. Tavaline näide on uue kasutaja registreerimise toiming, mis käivitab terve hulga teisi selle sündmusega seotud toiminguid:
- IP-aadressi kontrollimine rämpsposti andmebaasis,
- saadab uuele kasutajale e-kirja,
- boonuse lisamine soovitava kasutaja kontole,
- kontode loomine seotud teenustes,
- ja palju muud...
Näide kood mis vastutab kasutaja registreerimise eest, võib välja näha nii:
class RegistrationController < ApplicationController
def create
user = User.new(registration_params)
if user.valid? && ip_valid?(registration_ip)
user.save!
user.add_bonuses
user.synchronize_related_accounts
user.send_email
end
end
end
Okei, sa oled selle kodeerinud, kõik töötab, aga... kas kogu see kood on tõesti korras? Äkki saaksime seda paremini kirjutada? Esiteks, see rikub programmeerimise põhiprintsiipi - Single Responsibility, nii et kindlasti saaksime selle paremini kirjutada. Aga kuidas? Siinkohal tuleb appi juba mainitud PORO. Piisab, kui eraldada klass RegistrationService, mis vastutab ainult ühe asja eest: kõigi seotud teenuste teavitamine. Teenuste all peame silmas üksikuid tegevusi, mida me juba eespool välja tõime. Samas kontrolleris tuleb vaid luua objekt RegistrationService ja kutsuda sellele meetodit "fire!". Kood on muutunud palju selgemaks, meie kontroller võtab vähem ruumi ja iga äsja loodud klass vastutab nüüd ainult ühe tegevuse eest, nii et me saame neid vajadusel hõlpsasti välja vahetada.
klass 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
Kuid Plain Old Ruby Object võib osutuda kasulikuks mitte ainult kontrollerite jaoks. Kujutage ette, et teie loodav rakendus kasutab igakuist arveldussüsteemi. Sellise arve loomise täpne päev ei ole meile oluline, me peame ainult teadma, et tegemist on konkreetse kuu ja aastaga. Loomulikult võite määrata iga kuu esimese päeva päeva ja salvestada selle info klassi "Date" objekti, kuid see ei ole tegelik informatsioon, ega seda pole ka teie rakenduses vaja. PORO abil saate luua klassi "MonthOfYear", mille objektid salvestavad täpselt seda teavet, mida te vajate. Peale selle, kui rakendate selles moodulit "Comparable", on võimalik itereerida ja võrrelda selle objekte, täpselt nagu Date klassi kasutamisel.
klass MonthOfYear
include Comparable
attr_reader :year, :month
def initialize(month, year)
raise ArgumentError unless month.between?(1, 12)
@year, @month = aasta, kuu
end
def (other)
[aasta, kuu] [muu.aasta, muu.kuu]
end
end
Tutvustage mulle Railsi.
Rails'i maailmas oleme harjunud sellega, et iga klass on mudel, vaade või kontroller. Neil on ka oma täpne asukoht kataloogistruktuuris, nii et kuhu saab meie väike PORO armee panna? Mõelge mõnele võimalusele. Esimene mõte, mis tuleb meelde: kui loodud klassid ei ole ei mudelid, vaated ega kontrollerid, peaksime nad kõik panema kataloogi "/lib". Teoreetiliselt on see hea mõte, kuid kui kõik teie PORO failid maanduvad ühte kataloogi ja rakendus on suur, muutub see kataloog kiiresti pimedaks kohaks, mida kardate avada. Seetõttu ei ole see kahtlemata hea mõte.
AwesomeProject
├ ├├├├├├├├├├├├├╝
│ ├─kontrollerid
│ ├─ mudelid
│ └─views
│
└─lib
└─services
#all poro siin
Samuti võite nimetada mõned oma klassid mitte-ActiveRecord Models ja panna need kataloogi "app/models" ning nimetada need, mis vastutavad teiste klasside käitlemise eest, services ja panna need kataloogi "app/services". See on päris hea lahendus, kuid sellel on üks puudus: uue PORO loomisel tuleb iga kord otsustada, kas tegemist on pigem mudeli või teenusega. Nii võite jõuda olukorda, kus teil on oma rakenduses kaks pimedat kohta, ainult väiksemad. On veel kolmaski lähenemine, nimelt: nimevahega klasside ja moodulite kasutamine. Selleks tuleb vaid luua kataloog, millel on sama nimi kui kontrolleril või mudelil, ja panna kõik PORO failid, mida antud kontroller või mudel kasutab, sinna sisse.
AwesomeProject
├ ├├├├├├├├├├├├├╝
│ ├─kontrollerid
│ │ ├─registration_controller
│ │ │ │ └─registration_service.rb
│ │ │ └─registration_controller.rb
│ ├─models
│ │ ├─settlement
│ │ │ │ └─month_of_year.rb
│ │ │ └─settlement.rb
│ └─views
│
└─lib
Tänu sellele korraldusele ei pea selle kasutamisel klassi nimele eelnema nimeruumi. Te olete saanud lühema koodi ja loogilisema kataloogistruktuuri.
Vaadake mind!
On meeldiv üllatus, et POROt kasutades on teie rakenduse ühiktestid kiiremad ja lihtsamini kirjutatavad ning hiljem ka teistele paremini mõistetavad. Kuna iga klass vastutab nüüd ainult ühe asja eest, saab piiritingimusi kiiremini ära tunda ja neile hõlpsasti sobivaid testistsenaariume lisada.
describe MonthOfYear do
subject { MonthOfYear.new(11, 2015) }
it { should be_kind_of Comparable }
describe "uue instantsi loomine" do
it "initsialiseerib õige aasta ja kuu" do
expect { described_class.new(10, 2015) }.to_not raise_error
end
it "tekitab vea, kui antud kuu on vale" do
expect { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
end
end
end
Ma loodan, et me kohtume veel!
Esitatud näited näitavad selgelt, et PORO kasutamine parandab rakenduste loetavust ja muudab need modulaarsemaks ning seega lihtsamini hallatavaks ja laiendatavaks. Ühtse vastutuse põhimõtte omaksvõtmine hõlbustab vajadusel konkreetsete klasside vahetamist ja seda ilma teisi elemente segamata. Samuti muudab see nende testimise lihtsamaks ja kiiremaks. Lisaks on sel viisil Railsi mudelite ja kontrollerite lühikeseks hoidmine palju lihtsam, ja me kõik teame, et need kipuvad arenduse käigus asjatult suureks muutuma.