Sõltumata sellest, mida peame suureks koodiks, nõuab see alati lihtsat kvaliteeti - kood peab olema hooldatav. Õige taane, puhtad muutujate nimed, 100% katte katvus ja palju muud võivad töötada ainult punktini. Kõik koodid, mida ei saa hooldada ja mida ei saa muutuvate nõuetega suhteliselt hõlpsalt kohandada, on kood, mis loodab lihtsalt vananeda. Me ei pea võib-olla kirjutama suurt koodi, kui proovime luua prototüüpi, kontseptsioonitõendit või minimaalselt elujõulist toodet, kuid kõigil muudel juhtudel peaksime alati kirjutama hooldatava koodi. Seda tuleks pidada tarkvaratehnika ja -disaini põhikvaliteediks.
Selles artiklis käsitlen seda, kuidas ühe vastutuse põhimõte ja mõned selle ümber keerlevad tehnikad võivad teie koodile selle kvaliteedi anda. Suurepärase koodi kirjutamine on kunst, kuid mõned põhimõtted aitavad alati anda arendustööle suuna, mida ta vajab tugeva ja hooldatava tarkvara tootmiseks.
Peaaegu iga uue MVC raamistiku (MVP, MVVM või muu M **) raamat on täis halbu koodinäiteid. Need näited püüavad näidata, mida raamistik pakub. Kuid lõpuks annavad nad ka algajatele halbu nõuandeid. Näited nagu „oletame, et meil on mudelite jaoks see ORM X, mallimootor Y meie seisukohtade jaoks ja nii on meil kontrollerid, kes saavad kõigega hakkama ”, nad ei saavuta muud kui hiiglaslikud kontrollerid. Nende raamatute kaitseks on näited siiski mõeldud selleks, et näidata nende raamistiku lihtsust. Need ei ole mõeldud tarkvara kujundamise õpetamiseks. Kuid neid näiteid järgivad lugejad mõistavad alles aastate pärast, kui kahjulik on monoliitsete kooditükkide olemasolu teie projektis.
Mudelid on teie rakenduse keskmes. Kui teil on ülejäänud rakendusloogikast eraldi mudelid, on hooldus palju lihtsam, olenemata sellest, kui keeruliseks rakendus võib minna. Isegi keeruliste rakenduste korral võib mudeli hea rakendamine anda ülimalt väljendusrikka koodi. Ja selle saavutamiseks peate kõigepealt veenduma, et teie mudelid teevad ainult seda, mida nad on ette nähtud, ja ärge muretsege selle pärast, mida nende ümber ehitatud rakendus teeb. Samuti ei käsitleta seda, mis on andmete salvestamise aluseks olev kiht - kas teie rakendus sõltub SQL-i andmebaasist või salvestab see kõik tekstifailidesse?
Selle artikli jätkamisel saate aru, et suurepärane koodeks on seotud murede lahususega.
kuidas pythonit rahanduses kasutatakse
Olete ilmselt põhimõtetest kuulnud TAHKE : üksikvastutus, avatud-suletud, liskovi asendamine, liidese eraldamine ja sõltuvuse tühistamine. Esimene täht S tähistab ühtse vastutuse põhimõtet ( HAIGE ) ja selle tähtsust ei saa üle hinnata. Ma isegi ütleksin, et see on hea koodi jaoks vajalik ja oluline tingimus. Tegelikult võib igas halvasti kirjutatud koodis alati leida klassi, millel on mitu vastutust - form1.cs või index.php, mis sisaldab paar tuhat koodirida, pole kummaline ja ilmselt oleme kõik näinud. või tehtud.
Vaatame näite C # (ASP.NET MVC ja Entity raamistik). Isegi kui te pole a C # arendaja , väikese OOP-kogemusega saate hõlpsalt edasi liikuda.
public class OrderController { ... public ActionResult CreateForm() { /* * View data preparations */ return View(); } [HttpPost] public ActionResult Create(OrderCreateRequest request) { if (!ModelState.IsValid) { /* * View data preparations */ return View(); } using (var context = new DataContext()) { var order = new Order(); // Create order from request context.Orders.Add(order); // Reserve ordered goods …(Huge logic here)... context.SaveChanges(); //Send email with order details for customer } return RedirectToAction('Index'); } ... (many more methods like Create here) }
See on klass OrderController tavaline ja selle meetod on näidatud Loo . Sellistes kontrollerites näen sageli juhtumeid, kus klass ise Tellimus seda kasutatakse päringu parameetrina. Kuid eelistan kasutada spetsiaalseid päringuklasse. Muidugi veel kord HAIGE !
Pange tähele ülaltoodud koodilõigus, kuidas kontroller teab liiga palju 'tellimuse esitamisest', sealhulgas objekti salvestamisest, kuid mitte ainult Tellimus , saata e-kirju jne. See on ühe klassi jaoks lihtsalt liiga palju tööd. Iga väikese muudatuse korral peab arendaja muutma kogu kontrolleri koodi. Ja juhuks, kui mõni teine kontroller peab ka käske looma, kasutavad arendajad enamasti koodi kopeerimist ja kleepimist. Kontrollerid peaksid kontrollima ainult kogu protsessi ja mitte mahutama kõiki protsessi loogikaid.
Kuid täna on see päev, mil me lõpetame nende hiiglaslike autojuhtide kirjutamise!
Eemaldame kõigepealt kogu äriloogika kontrollerist ja teisaldame selle klassi OrderService :
public class OrderService { public void Create(OrderCreateRequest request) { // all actions for order creating here } } public class OrderController { public OrderController() { this.service = new OrderService(); } [HttpPost] public ActionResult Create(OrderCreateRequest request) { if (!ModelState.IsValid) { /* * View data preparations */ return View(); } this.service.Create(request); return RedirectToAction('Index'); }
Kui see on tehtud, teeb kontroller nüüd ainult seda, mida ta peaks tegema: protsessi juhtima. Ta teab ainult vaateid, klasse OrderService Y OrderRequest - minimaalne teave, mis on vajalik teie töö tegemiseks, see on taotluste haldamine ja vastuste saatmine.
Seega muudetakse harvadel juhtudel kontrolleri koodi. Muud komponendid, nagu vaated, päringuobjektid ja teenused, võivad muutuda, kuna need on seotud ärinõuetega, kuid mitte draivereid.
See on see HAIGE ja sellele printsiibile vastavat koodi kirjutamise tehnikat on palju. Selle näiteks on sõltuvuse süstimine (milleks on ka kasu kirjutage testitav kood ).
Ühtse vastutuse põhimõttel põhinevat suurt projekti ilma sõltuvuse süstimiseta on raske ette kujutada. Vaatame uuesti oma klassi OrderService :
public class OrderService { public void Create(...) { // Creating the order(and let’s forget about reserving here, it’s not important for following examples) // Sending an email to client with order details var smtp = new SMTP(); // Setting smtp.Host, UserName, Password and other parameters smtp.Send(); } }
See kood töötab, kuid pole eriti ideaalne. Selleks, et mõista, kuidas loomise klassi meetod töötab OrderService on sunnitud mõistma SMTP . Ja jällegi on kopeerimine ja kleepimine ainus viis selle kasutamise kordamiseks SMTP kus vajalik. Kuid väikese ümbertegemisega, mis võib muutuda:
public class OrderService { private SmtpMailer mailer; public OrderService() { this.mailer = new SmtpMailer(); } public void Create(...) { // Creating the order // Sending an email to client with order details this.mailer.Send(...); } } public class SmtpMailer { public void Send(string to, string subject, string body) { // SMTP stuff will be only here } }
Parem! Aga klass OrderService teate ikka veel palju e-kirjade saatmise kohta. Te vajate täpselt klassi SmtpMailer meili saatmiseks. Mis siis, kui me tahame seda hiljem muuta? Mis siis, kui me tahame oma arenduskeskkonda saatmise asemel printida spetsiaalsesse logifaili saadetud meilisisu? Mis siis, kui tahame oma klassi proovile panna OrderService ? Jätkame liidese loomisega refaktoreerimisega IMailer :
public interface IMailer { void Send(string to, string subject, string body); }
SmtpMailer rakendab seda liidest. Samuti kasutab meie rakendus IoC konteinerit ja saame selle nii konfigureerida IMailer klass rakendama SmtpMailer . OrderService saab muuta järgmiselt:
seal, üksi, ma olen
public sealed class OrderService: IOrderService { private IOrderRepository repository; private IMailer mailer; public OrderService(IOrderRepository repository, IMailer mailer) { this.repository = repository; this.mailer = mailer; } public void Create(...) { var order = new Order(); // fill the Order entity using the full power of our Business Logic(discounts, promotions, etc.) this.repository.Save(order); this.mailer.Send(, , ); } }
Nüüd liigume edasi! Kasutasin seda võimalust, et teha veel üks muudatus. OrderService nüüd tugineb liidesele IOrderRepository suhelda komponendiga, mis salvestab kõik meie tellimused. Teid ei huvita enam see, kuidas seda liidest rakendatakse või milline salvestustehnoloogia seda toidab. Nüüd klass OrderService teil on ainult kood, mis käsitleb tellimuste äriloogikat.
Nii, kui testija leiab e-kirjade saatmisel midagi valesti, teab arendaja täpselt, kust otsida: klass SmtpMailer . Kui allahindlustega oli midagi valesti, teab arendaja veel kord, kust otsida: klassi kood OrderService (või juhul, kui olete nõustunud HAIGE südamest siis võib DiscountService ).
Siiski ei meeldi mulle endiselt OrderService.Create meetod:
public void Create(...) { var order = new Order(); ... this.repository.Save(order); this.mailer.Send(, , ); }
E-kirja saatmine ei kuulu tegelikult peamise tellimuse loomise voogu. Isegi kui rakendus ei suuda meili saata, luuakse tellimus õigesti. Kujutage ette ka olukorda, kus peate kasutaja seadete alale lisama uue valiku, mis võimaldab neil pärast tellimuse edukat esitamist e-kirjade saamisest loobuda. Selle kaasamiseks meie klassi OrderService , peame kasutama sõltuvust: IUserParametersService . Lisage asukoht ja teil on juba uus sõltuvus, IT-tõlkija (õigete meilide loomiseks kasutaja valitud keeles). Mitmed neist toimingutest on tarbetud, eriti idee lisada nii palju sõltuvusi ja lõpetada ekraaniga mitte sobiva konstruktoriga. Leidsin a suurepärane näide sellest Magento koodibaasis (a CMS PHP-s kirjutatud pood) klassis, millel on 32 sõltuvust!
Mõnikord on raske ette kujutada, kuidas seda loogikat lahutada ja Magento klass on tõenäoliselt ühe sellise juhtumi ohver. Sellepärast meeldib mulle “sündmuste juhitud” viis:
namespace .Events { [Serializable] public class OrderCreated { private readonly Order order; public OrderCreated(Order order) { this.order = order; } public Order GetOrder() { return this.order; } } }
Iga kord, kui tellimus luuakse, selle asemel, et saata otse klassist e-kiri OrderService , luuakse eriürituste klass Tellimus on loodud ja sündmus genereeritakse. Kusagil rakenduse sündmuste käitlejates see konfigureeritakse. Üks neist saadab kliendile meili.
namespace .EventHandlers { public class OrderCreatedEmailSender : IEventHandler { public OrderCreatedEmailSender(IMailer, IUserParametersService, ITranslator) { // this class depend on all stuff which it need to send an email. } public void Handle(OrderCreated event) { this.mailer.Send(...); } } }
Klass Tellimus on loodud on tähistatud kui Serialiseeritav muideks. Saame selle sündmuse karbist välja tulla või salvestada järjestikku (Redis, ActiveMQ või muu) ja töödelda seda eraldi protsessis / lõimes sellisena, mis veebipäringuid haldab. Sisse see artikkel , selgitab autor üksikasjalikult, mis on sündmustepõhine arhitektuur (ärge pöörake tähelepanu äriloogikale OrderController ).
Mõni võib väita, et nüüd on tellimuse loomisel raske aru saada, mis juhtub. Kuid see pole üldse tõsi. Kui tunnete end nii, kasutage lihtsalt oma funktsionaalsuse eeliseid SIIN . Klassi kõigi kasutusalade leidmisel Tellimus on loodud kell SIIN , näeme kõiki sündmusega seotud toiminguid.
Kuid millal peaksin kasutama sõltuvussüsti ja millal peaksin kasutama sündmustel põhinevat lähenemist? Sellele küsimusele pole alati lihtne vastata, kuid lihtne reegel, mis võib teid aidata, on sõltuvuse süstimine kõigi rakenduse põhitegevuste jaoks ja sündmustepõhine lähenemine kõigi kõrvaltoimete jaoks. Näiteks kasutage sõltuvuse süstimist selliste asjadega nagu, looge klassis tellimus OrderService koos IOrderRepository , samuti meilide saatmise delegeerimine, mis ei ole tellimuste loomise põhivoo oluline osa, mõnele sündmuste käitlejale.
Alustame väga olulise kontrolleriga, ainult ühe klassiga, ja lõpetame keeruka klassikogumikuga. Nende muudatuste eelised ilmnevad mõnevõrra näidetest. Kuid nende näidete täiustamiseks on endiselt palju võimalusi. Näiteks meetod OrderService.Create saab kolida omaette klassi: OrderCreator . Kuna tellimuste loomine on äriloogika iseseisev üksus, mis järgib ühtse vastutuse põhimõtet, on loomulik, et sellel on oma klass koos oma sõltuvuste komplektiga. Samamoodi saab tellimuse kustutamist ja tühistamist rakendada ka nende endi klassides.
Kui kirjutasin tihedalt seotud koodi, mis oli sarnane selle artikli esimese näitega, võib mis tahes nõude muudatus, olgu see nõnda väike, põhjustada palju muudatusi koodi teistes osades. HAIGE aitab arendajatel kirjutada kood, mis on 'paaritu', kus igal klassil on oma töö. Kui mõni selle töö spetsifikatsioon muutub, teeb arendaja muudatusi ainult selles konkreetses klassis. Vähem tõenäoline, et muudatus rikub kogu rakendust, kuna teised klassid peaksid oma tööd tegema nagu varem, välja arvatud juhul, kui nad olid algselt katki.
Nende tehnikate abil koodi väljatöötamine ja ühtse vastutuse põhimõtte järgimine võib tunduda heidutav ülesanne, kuid jõupingutused tasuvad end ära, kui projekt kasvab ja areng jätkub.