Rubiini stringi tähed VS Väärtusobjektid. Ületreening?

Algne postitus:

Juhend rubriigist Literal vabanemiseks koos Rails 5 Atribuutide API-ga

Minu eelmises artiklis näitasin, kuidas saate oma rakenduse kujunduse parendamiseks kasutada RSON 5 atribuutide API-t koos JSONB-ga ja väärtustada objekte.

Täna tahan teile näidata, kuidas saab atribuutide API-d rakendada primitiivse kinnisidee antireaktsiooni mustri jaoks.

Primitiivsed andmetüübid on keele peamised sisseehitatud ehitusplokid. Tavaliselt kirjutatakse neid int, stringina või konstantidena. Kuna selliste väljade loomine on palju lihtsam kui täiesti uue klassi loomine, põhjustab see kuritarvitusi. Seetõttu muudab see lõhn üheks kõige tavalisemaks.

  • Primitiivsete andmetüüpide kasutamine domeenideede esindamiseks. Näiteks täisarvu kasutamine rahasumma või telefoninumbri stringi tähistamiseks.
  • Muutujate või konstantide kasutamine teabe kodeerimiseks. Sageli esinev juhtum kasutab konstante kasutajate rollidele või mandaatidele viitamiseks (nt USER_ADMIN = 1;).
  • Stringide kasutamine andmemassiivides väljade nimedena.

Vaatame seda koodi.

klassikava :: kanal  {kus (tüüp: "# {type}")}
  lõpp

  atribuut: astmed, plaan :: kanal: astmed: tüüp.uud
lõpp

Pidevad CALCULATION_METHODS on määratletud kui külmutatud stringide massiiv ja tähistab, kuidas kanalit tuleks arvutada. Alguses näeb kõik päris hästi välja.

klassi plaan :: CreatePlanForm 

Aga äkki…

klassi komisjon :: kalkulaator :: libisev skaala 

Ja jälle …

klassi komisjon :: Kalkulaator
  #…

  klass << ise
    def kalkulaator (preimage)
      juhtum preimage.channel.calculation_method
      kui 'sliding_scale_net_origination_fee',
           'libisev_skaala_originatsiooni_maht'
        Komisjon :: Kalkulaator :: SlidesScale
      kui "tasane"
        Komisjon :: kalkulaator :: korter
      kui "levib"
        Komisjon :: Kalkulaator :: Spread
      lõpp
    lõpp
  lõpp

  #…
lõpp

See tundub minu jaoks tõesti kohutav. Kui muudame väärtusi jaotises CALCULATION_METHODS, on suur võimalus, et unustame need operaatorite korral lihtsalt värskendada. Parem oleks vältida sellise ohtliku koodi kasutamist.

Primitiivsed kinnisideed sünnivad nõrkuse hetkedel. “Ainult väli andmete salvestamiseks!” Väitis programmeerija. Primitiivse välja loomine on palju lihtsam kui täiesti uue klassi tegemine, eks? Ja nii see tehti. Siis oli vaja veel ühte välja ja see lisati samal viisil.

Tüüpide “simuleerimiseks” kasutatakse sageli primitiivseid elemente. Nii et teil on eraldi andmetüübi asemel numbrite või stringide komplekt, mis moodustavad mõne olemi lubatud väärtuste loendi. Nendele konkreetsetele numbritele ja stringidele antakse konstandite kaudu hõlpsasti mõistetavad nimed, mistõttu need on laialt levinud.

Esimene lähenemisviis on reaktor CALCULATION_METHODS muutuda konstantseks neljale sõltumatule konstandile, nii et saame midagi sellist.

klassikava :: kanal 

Kuid jällegi ei tundu see kood mulle hea. Teine lähenemisviis oleks selle koodi taasreageerimine AR Enum API abil, kuid me teeme seda muul viisil.

Proovime uuesti kasutada atribuutide API-t. Veerus plan_channels on arvutusmetoodika veerg, mis on string. Peaksime selle atribuudina lisama kavva Plan :: Channel model ja määrama tüübi.

klassikava :: kanal  {kus (tüüp: "# {type}")}
  lõpp

  atribuut: astmed, plaan :: kanal: astmed: tüüp.uud
  atribuut: arvutusmeetod, plaan: kanal: kanal: arvutusmeetod: tüüp.uuend
lõpp
nõuda 'kuivalgatajat'

klassi plaan: kanal: arvutusmeetod
  pikendage Kuiv :: Initializer

  LÕHK = 'tasane' .külm
  private_constant: FLAT

  SPREAD = 'levik'.külm
  private_constant: SPREAD

  SLIDING_SCALE_NET_ORIGINATION_FEE = 'sliding_scale_net_origination_fee'.freeze
  privaatne_konstant: SLIDING_SCALE_NET_ORIGINATION_FEE

  SLIDING_SCALE_ORIGINATION_VOLUME = 'sliding_scale_origination_volume'.freeze
  privaatne_konstant: SLIDING_SCALE_ORIGINATION_VOLUME

  SLIDING_SCALE = [
    SLIDING_SCALE_NET_ORIGINATION_FEE,
    SLIDING_SCALE_ORIGINATION_VOLUME
  ] .külm
  private_constant: SLIDING_SCALE

  param: väärtus, proc (&: to_s)

  def iseväärtused
    [FLAT, SPREAD, SLIDING_SCALE] .flatten
  lõpp

  def to_s
    väärtus.to_s
  lõpp

  väärtused.jagu teha | v |
    define_method "# {v}?" teha
      väärtus == v
    lõpp
  lõpp

  def sliding_scale?
    SLIDING_SCALE.kaasa? (Väärtus)
  lõpp

  klass Tüüp 

Plaan :: kanal :: CalculationMethod sai väärtusobjektiks. Mida sai arendaja refaktoriseerimisest?

  • Primitiivsete väärtuste komplekti asemel on programmeerijal täieõiguslik klass koos kõigi eelistega, mida objektorienteeritud programmeerimisel pakkuda on (andmete tippimine klassi nime järgi, tüübi vihje jne).
  • Andmete valideerimise pärast pole vaja muretseda, kuna saab seada ainult eeldatavad väärtused.
  • Kui CalculationMethod loogika laieneb, kogutakse see ühte kohta, mis on sellele pühendatud.
klassi komisjon :: kalkulaator :: libisev skaala 
klassi komisjon :: Kalkulaator
  klass << ise
    def arvutama (eelvaade)
      saada (preimage) .kalkulatsiooni
    lõpp

    def saama (preimage)
      kalkulaator (preimage) .new (preimage)
    lõpp

    def kalkulaator (preimage)
      kui preimage.channel.calculation_method.sliding_scale?
        Komisjon :: Kalkulaator :: SlidesScale
      elsif preimage.channel.calculation_method.flat?
        Komisjon :: kalkulaator :: korter
      elsif preimage.channel.calculation_method.spread?
        Komisjon :: Kalkulaator :: Spread
      lõpp
    lõpp
  lõpp

  attr_reader: eelpilt

  delegaat: grupp,: laen, kanal: kanal = => eelkujutis

  def initsialiseerima (preimage)
    @preimage = eelkujutis
  lõpp

  def arvutada
    preimage.amount = määr * laetud_maht / 100
  lõpp
lõpp

Sama tuleks teha konstantsete TÜÜPidega. Ja pärast seda näeb meie mudel kuiv ja puhas välja.

klassikava :: kanal  {kus (tüüp: "# {type}")}
  lõpp

  atribuut: astmed, plaan :: kanal: astmed: tüüp.uud
  atribuut: arvutusmeetod, plaan: kanal: kanal: arvutusmeetod: tüüp.uuend
  atribuut: tüüp, plaan: kanal: tüüp: tüüp: uus
lõpp

Viimane asi, mida tuleks teha, on ümber nimetada veerg plan_channels # tüüp. Ma arvan, et allikas oleks õige nimi ja pärast seda on meil oma mudeli lõplik versioon.

klassikava :: kanal  {kus (allikas: "# {allikas}")}
  lõpp

  atribuut: astmed, plaan :: kanal: astmed: tüüp.uud
  atribuut: arvutusmeetod, plaan: kanal: kanal: arvutusmeetod: tüüp.uuend
  atribuut: allikas, Plaan :: Kanal :: Allikas :: Tüüp.uue
lõpp

Kas see pole ülemäärane tehnika? Nööri asemel täiesti uus klass? Kas tõesti? Vastus sellisele küsimusele sõltub alati kontekstist, kuid harva leian, et see on liiga insenertehniline.

Võite väita, et 15 minutit on palju, võrreldes nulminutitega, mis kuluks, kui kasutaksite lihtsalt stringi. Pinnale tundub see kehtiv vastuargument, kuid võib-olla unustate, et primitiivse stringi korral peate ikkagi kirjutama valideerimise ja äriloogika stringi ümber ja peate meeles pidama, et rakendage seda loogikat järjepidevalt. kogu teie koodialuses. Ma arvan, et selle tegemiseks ja tõrkeotsingu vigadele, mis tekivad siis, kui keegi unustab mõnda neist reeglitest koodi baasi mõnes teises osas kasutada, kasutada rohkem kui 15 minutit.

Avastage rohkem sisu saidil JetRockets.pro

Igor Aleksandrov
CTO JetRocketsis