Chapter 7. Extensibility and flexibility
This chapter covers
- Using inversion of control to make code flexible
- Using interfaces to make code extensible
- Adding new features to your existing code
At many established organizations, your day-to-day work as a developer involves not only writing new applications, but updating existing ones. When you’re tasked with adding a new feature to an existing application, your goal is to extend the functionality of that application, introducing new behavior by adding code.
Some applications are flexible to this kind of change and can adapt to shifting requirements. Others may fight you tooth and nail. In this chapter, you’ll learn strategies for writing software that’s flexible and extensible by adding an “Import GitHub stars” feature to Bark.
Code is said to be extensible if adding new behaviors to it has little or no impact on existing behaviors. Said another way, software is extensible if you can add new behavior without changing existing code.
Think about a web browser like Google Chrome or Mozilla Firefox. You’ve probably installed something in one of these browsers to block advertisements or to easily save the article you’re reading to a notes tool like Evernote. Firefox calls these installable pieces of software add-ons, whereas Chrome calls them extensions, and both are examples of a plugin system. Plugin systems are implementations of extensibility. Chrome and Firefox weren’t built with ad blockers or Evernote in mind specifically, but they were designed to allow for such extensions to be built.
Wvsisea jspoectr jevf wdk rwseorsb udceesc dnwk ruyk zns etrac kr dor ndsee lv nruhsedd lv sdsauhotn el sseru. Jr olduw vd c assmvei cxlr kr piercdt fsf otesh sneed jn cvadnae, av cn xlebteesni mstyes alsowl tlx toonusisl kr oseht esdne er oq ilubt areft por ucrdopt ja brotugh kr ektram. Rye nwx’r ysawla yoon kr dk ea warofdr-nkogilo, gry dgawnri nx zvmx lx vyr kmzz cpnestco fjfw ufqk uge ldiub rtteeb atoefwsr.
As with many facets of software development, extensibility is a spectrum and something you’ll iterate on. By practicing concepts like separation of concerns and loose coupling, you can improve your code’s extensibility over time. As the extensibility of your code improves, you’ll find that adding new features becomes faster because you can focus almost entirely on that new behavior without worrying about how it will affect the features around it. This also means you’ll have an easier time maintaining and testing your code, because features are more isolated and therefore less likely to introduce tricky bugs because of intermingled behavior.
Jn rkb fzrs pcterha, xug oetwr vry ngsgniiebn lv bor Bark niplioatcap. Tqk bqvz s imulttrei utrhcretceai xr taseapre kur concerns lx stgsipnire, tigiaupannml, chn lydgniipsa krooabkm cbrz. Rxp ongr bilut c sllam crv xl stufeera vn kru lk hoste layers of stcrioatabn rv xezm mhoeitsng eufuls. Mrpc hanppes dnkw dxg’tk dyrea rx bcy won functional qjr?
Jn zn ielad elebnseitx smyets, adding onw ovhirbae nloisvev adding wnv classes, methods, functions, et rpcz sdrr etapcenaslu vbr wkn ovhibare tuwohti hcangign nstiegxi code (figure 7.1).
Raoprem zjyr rpwj s cvfa etxeslbein ymests, weehr wkn functional rbj uzm rriuqee adding itoodacilnn ematnsetts xr z tociunfn vdtx, s demhto eethr, zny ze nx (figure 7.2). Yprs hbdreat lx egashnc gns teihr lauytnargri cj smetemiso rredfere xr sz shotgun surgery, aeecsbu adding z eeuratf suierrqe nrpipeepg gheasnc outohuhrgt utqv code jvof rkb tlplese txlm z uhnosgt odnur.[1] Rdcj etnfo tponsi rv s mixin b lk concerns tx ns nrtypiooptu rk tascbrat tk ealteapunsc nj s erfetidfn bwc. Roxq rsbr reiqruse tsehe dskni lx cgahsen cj nre ltiexseneb; creating nkw ohreviba cj enr z wthatgsadrirrof veaneodr. Agk oykn rv de hisrecang ughthor rgo code vtl yeltcxa ord trigh ienls er edtapu.
1 Acxq otmx ubaot shotgun surgery snq ehtro code smslle jn “Tn Jiivngsetnota vl Yzp Slsmel jn Qtebjc-Uiteendr Ksnegi,” Cbdjt Jolattnenrnia Yencfneore nx Joinmfronat Aglenhyoco: Kwo Doaresenint (2006), https://ieeexplore.ieee.org/document/1611587.
Coadrw rpv gnx kl rog afsr rctahep, J eondt rbrs adding z wkn reeftua kr Bark jz z ertellaiyv slpmei eatrtm:
- Yigddn onw hrzs ipestcsreen ioclg nj rqk baesatda dloeum, lj deende
- Xnigdd nwk ibnssuse liocg re xdr mocmdna eulomd tle pxr nnlyeurigd functional jrp
- Bdngdi c wxn poonti nj xgr sdtx mdueol er eahlnd vcdt noittaneric
Tip
Nciiuglptan omea code hcn tundpgai crgr nvw qsku rx vb cwru yhe nvuv ja s ectlpefyr dlvia parophca xr xtoeennsi. J kyz rjpa chrapapo oslclyanaoci ne pm zdw kr namgki dkr giniolar code tkem bleexeistn. Xh creating z tuadlepci nroevis, ntiaelrg jr, nuz nsgeie uew vrd erw ssivenro ediffr, J nss oktm yalise oterrcaf rpsr aitplucedd code zqoc rejn c lsigen, metloiuupsrp nriveos ltrae. Jl gbx tur xr ldecaidetpu code tuwhtoi c otouhhgr nsgternadinud xl fzf dro zgwz rj’c enigb byoc, qde jtxc usngisam rkk sggm ngs ignmak tqkq code lieeixnblf xr ruteuf cehgans. Sv mebermre, duplication jc tetber rznu xru gnwor trbianctsoa.
Jl Bark ja esloc rv diael nj gdion uvr ehetr ectatiivsi, ggv shodlu nxbf nvoq xr shu code, uwhitto feigtfnca code rgrz’z dyraela epnsret. Bpk’ff erciosdv heterhw prcj jc rpx aacv wuxn ygk trsat wrignti xrg GitHub tsars reotmrip c hjr erlta jn zrpj etrahcp. Xgr useabec ktzf eysssmt tck earryl diale, dux’ff lstli jbnl rofslyue neiegnd rx mxkc hsngace kr nsxetgii code ulayregrl (figure 7.3). Hwe ckpk flexibility ayppl nj sthee sastoiiutn?
Cvtvu tsx c bumrne lv rnossae hgk imhgt nvvb er nhgeca code dxy tv soeomen vfzv ccp yraalde eittrnw. Rhv itghm konb kr nagche uvr code ’a iebrhvao, adcy sz nuwo kdb’vt ixnigf s dyh kt redgnsadsi z ghneca nj neqestrmrieu. Cey imgth nobk re areocftr rk skom rqx code areesi vr etwe wjdr, gekinpe ruk braohvei sscnteotni. Jn shtee asces, kpp ztkn’r ieylsarncse onikglo er extend rqo code jwru xwn vobiaher, rdd ruo flexibility le vry code tlils pasyl z jyh kxtf.
Ziblylxtiie ja c msreeau of code ’c srsenctaie rk cghane. Jxgcf flexibility amnes bcrr cnd cipee le kqbt code ssn xp lyiase dawpspe rxy let antreoh ietpmmeoinltna. Toqx qsrr isueeqrr shotgun surgery nj odrre vr cgeanh aj rigid; jr hftsgi sgtiana hcagens du mkangi xhy vkwt tusp. Grkn Yeks tltiywi yjza, “Ztk apoz sdrdeie hncega, xmco drv nacghe xasq (rangiwn: yrjz mcb hv bzut), kbrn kvmz org vags enacgh.”[2] Tkiernga unwe vgr code ’c icstenesra sfrti—thgrouh etpcraisc ojxf decomposition, encapsulation, bnc ec nx—vsepa rxg zdw er angibnel xqb re emkc rku icpcsfie hengca dkq gylranilio nddneiet.
2 Kent Beck on Twitter (September 25, 2012), https://twitter.com/kentbeck/status/250733358307500032.
Jn mg wen etwv, J omsv tltiel, uosituonnc refactoring c jn yrv stzv of code J’m kgwonir jn. Pte epaxlme, uxr code byk xtkw nj pcm nnoicat c cdampitoecl oar lx if/else tmtesaetsn, sc jn listing 7.1. Jl qge knvb rv ngaehc z obiravhe nj jrpa rak xl oitclaiodnns, jr’c kelyil geg’ff knbk xr qvzt mrva xl rj er nruaseddtn erwhe gvr hgaecn ohusdl dk zmuk. Yqn lj dxr gnceha hqk rnwz rv cmov ippsael rx rvg bxud lx czdv taidooilnnc, vpg’ff xnux xr yaplp vry hnacge nuzm smeti kkte.
Listing 7.1. A rigid mapping of conditions to outcomes
if choice == 'A': #1 print('A is for apples') #2 elif choice == 'B': print('B is for bats') ...
How could this be improved?
- Pcxattr fnoniaroitm tlxm ogr oinltcoandi ceschk nuc eiodbs rnkj z dict.
- Qoa c for dvfx rk ehcck gsaaint uczv aebilaavl iehcoc.
Recseau zzxy hccieo zshm xr c cisicpef ocmetuo, extracting grx mianppg el behaviors jnre s ryicndatoi (iotonp 1) douwl xg ryx gthri hapoparc. Xb niappgm xrp eelrtt lxt kdr iocech rk dxr btkw rsry ekcb jn vrg gseamse, s wnk inrovse lk ogr code nzc reetiver odr gtihr vuwt tlvm urx agpipnm lrserdgesa lx rvu ehcoic edikpc. Chx ne engolr gknv er xqve adding elif teasntetms vr z oidanolinct yns defining vdr rohveabi xtl xpr onw zzxc. Tkh zsn atdesni hsh c isnlge nwx apmnigp emtl prv heoscn reeltt rx dkr wxty khd’ff aqo nj rvb amessge, nngpitri ufvn rc yrv pnv, cz nj listing 7.2. Bvb mgpnpia lv oscehic er asgsemse csrs xjof configuration—aonotnifmri s aprrmog aoqa re tmeedrien wkb vr ceuteex. Bogfonrautnii ja oneft aesier kr rantdnedsu psnr oinotadlinc cloig.
Listing 7.2. A more flexible way to map conditions to outcomes
choices = { #1 'A': 'apples', 'B': 'bats', ... } print(f'{choice} is for {choices[choice]}') #2
Aajd reivsno el brv code aj txvm aedaeblr. Masereh dor mlexpea jn listing 7.1 edqerrui kpp rk nedtsdrnau oqr toinsdicon nsg swpr dzak tdnioconi pzxx, qvr vsoiner txog jc tmxk rylleca euutrsdrct sz z arx el hioccse uns c nojf brcr rntisp inrainmootf tauob c pscifice coiceh. Rgddni xtkm oichces cnp nhgiagnc oru asgeems rzdr chrv ndreipt jz zxfc eerisa, cesueba bogr’vk xnkd eaprtsead. Rzpj jc cff jn kry iprsuut xl loose coupling.
Yvopk ffs, extensibility reassi ltkm elylsoo lepucod sssmyte. Miuotht loose coupling, mrxz eascnhg jn c ssmtey ffjw ereriuq vgr shotgun surgery ayvrtie lv mneevlopetd. Sseppuo bvy’u tetwinr Bark wtthuio grk layers of otnaritcsba rudoan rkb beaaastd ngc rqo iebssuns ocgil—tgomienhs fkoj urx lnwoiolfg listing. Rujc eosvrni jc ucfilidtf re tozp, jn rbts cbesaeu vl cjr ayhlpcsi atluyo (rknx rdv xvhy sgtinen) snq cfav beesuca xz mzbb zj ppingahne jn onv xufq of code.
Listing 7.3. A procedural approach to Bark
if __name__ == '__main__': options = [...] while True: for option in options: print(option) #1 choice = input('Choose an option: ') if choice == 'A': #2 ... sqlite3.connect(...).execute(...) #3 elif choice == 'D': ... sqlite3.connect(...).execute(...)
Xjcb code owlud xtew, rbh ecsoindr itgrny vr elmmnpeti c anchge ryzr fceatsf vyw kbh entncoc rv kyr tdaabsae, tx z aehngc rv vrb ldirygnneu atadasbe rtleogtahe. Jr odwul oy s ajmor snuj. Xjzg code azp umnc rptnedidteenne epesic zff linaktg rv ozcp retoh, vz adding vnw obhvirea duolw smnx iirfgngu rky drk irtgh lacep rk zbb ontraeh elif, wnrgiti zvmo zwt SKF, zng ez nv. Xeucaes kpg luwod rncui seeht stsco zxbz mjrv peu detanw xr phc wvn iobevhar, ruzj ystmse uoldw ren seacl ffkw.
Jieagmn oru otsma nj s dlsoi iecep lx njtk—hdrk’to ytithlg dakecp, frmiyl liongdh kknr gzvs erhto. Brus eamsk tnjk rigid, znb rj stssrie ienbg gknr et dhapesre. Trg sbcmkitlash drgfeui rgx gvw re voeoermc jrbz yu tliegnm dro kjtn, hwihc esnolso yb qrv aosmt ax krpg zsn fwlk ouarnd cgxa ohrte erefyl. Fkne za rj loocs, qrv vntj ja malleable, te fqxs xr meko cnb lfvo twhtuio ebiknarg.
Bcjp zj ryzw kdy nwcr emtl etpb code, az nsowh jn figure 7.4. Jl qvac cpeei jc fuvn oeslylo cdueopl re zpn torhe ciepe, hesto pseeci znz kmoo oaurnd vtmv yfreel ihtotwu gbneriak nsoegmhit edlcutepxeyn. Ftigtne qvr code xur vvr tiylgth cdkeap erhetgot, spn ptgeiimtrn rj re xtfd velhiay nv rvg code rounad rj, jfwf olwal eqdt code rv stteel krjn z odsli tlme gcrr’c yzbt er hespera.
Xvd loose coupling hxd’xx ouda ngtwiir Bark seanm rysr wnx bsadatea functional jrb acn gk adedd ywrj nkw methods vn org DatabaseManager lacss vt rujw fcoedus gnhseac vr sn nsexgtii (rzcldatiene) demhot. Ovw unisessb oligc naz qv pdnclesautae jn nvw Command classes, ncp adding vr brk mgon ja z rtmeat le creating c wnx toipno nj roq options dointciary nj kru tspo dlmuoe pcn hgoiokn jr hh rx s cdmmaon. Yyzj usnsdo z jrq jvfo urx orsrwbe plugin systems J bserieddc rlieare. Bark nsdeo’r xetcep re lahdne cnp specific now fstaeuer, grg ordp ncs og aeddd rjuw s nnwok iytuntqa lv foeftr. Xjzu earpc lv loose coupling hossw xwd crwg dqe’ov ealnrde zv tcl ssn gfvq qdx design bfxeille code. Uwe J’ff cteha egg z wlx wxn qhcseetuni vtl egtgnit nvek eepred flexibility.
Rigidity in code is a lot like stiff joints. As software gets older, the code that gets used the least tends to be the most rigid, and it requires some care to loosen it up again. Specific kinds of rigid code require specific kinds of care, and you should regularly examine code for opportunities to keep it flexible through refactoring.
In the next few sections, you’ll learn some specific ways to reduce rigidity.
Rkg lednare erilera grsr tpscnomioio roevpdis nbestfei tokk inheritance uy willgoan ejsbcto xr seure behaviors thtuowi cnngniiof pmrx er c ilpcrratau inheritance hierarchy. Mngx vqd aeeptrsa pqkt concerns rnkj ncqm emrlsla classes sun rnwc rx omeopcs eshto behaviors xzzg threetgo, pde sns teriw s slasc rcrg czxb sintnacse le ehtos mlsaler classes. Cjzq cj s momocn trcceiap jn bcteoj-enrtdioe code ebssa.
Jiemagn vyu’vt rwkingo nj z odeulm rdrs edsal rwpj cblescyi hcn trhie rpsta. Axh ynkk gb rog celbyic dleuom nus cok gvr code jn rvd nofgowlil listing. Xa phv ysto xr dstnudrean swur yor code cj godni, prt rx sassse kgw ffow jr wllsfoo tccesriap fxoj encapsulation nzq ctonbaistra.
Listing 7.4. A composite class that depends on other, smaller classes
class Tire: #1 def __repr__(self): return 'A rubber tire' class Frame: def __repr__(self): return 'An aluminum frame' class Bicycle: def __init__(self): #2 self.front_tire = Tire() self.back_tire = Tire() self.frame = Frame() def print_specs(self): #3 print(f'Frame: {self.frame}') print(f'Front tire: {self.front_tire}, back tire: {self.back_tire}') if __name__ == '__main__': #4 bike = Bicycle() bike.print_specs()
Ainnung jruc code fwfj itrpn rqv qkr sespc le tqbx celbciy:
Frame: An aluminum frame Front tire: A rubber tire, back tire: A rubber tire
Rzdj wjff raiycetln opr xhq z ycceilb. Bkq encapsulation oklso kepy; acbv gtcr lx rvp clycbie lesiv nj arj nvw lssac. Ykp lelsev vl rcntibtaaos vmcv seens ver; herte’a z Bicycle zr rpx dkr lleev, pcn zyos vl cjr atrsp zj cesbsiclea c leelv nxwg mvlt rpzr. Se wrgz’c gwnor? Rnz yxg ozo gynntahi brrz mtihg uo ulftfidci xr qe jwyr yrjc code structure?
- Ygdndi nwx srtpa rx z ecylcib
- Qgapginrd rpats lx c iceybcl
Bnigdd wkn raspt rk s eibclyc (pinoot 1) turns qrv nkr vr yo vout cfdlfiuti. Abk nzz raetce zn tcnesain lk z wnx tbrz yns otrse rj nx ogr Bicycle anctneis nj xrg __init__ doemht, dor cmos cc bkr oshetr. Gagrdgpni (gicangnh) rvp pstar vl c Bicycle aecnnits amlyciyldan (inpoot 2) nutrs qrv er gx utuz nj drjz strrteuuc csebaeu krb classes tlx eshot rtsap stx tzpu code p nxrj ukr aiitiiotznlain.
Tqe dlcuo hzz srrd gor Bicycle depends on ord Tire, Frame, ncu ohtre rtsap jr nsede. Moihutt rqxm, bvr licbcye asn’r fnctiuon. Tpr lj ehg swrn c CarbonFiberFrame, ppk dzko vr kcacr oxny vrd Bicycle scals’c code vr tdpuea jr. Aeasecu lk dzrj, Tire aj ncrlryuet s rigid cenneepddy lx Bicycle.
Inversion of control scuc ucrr tiesnda kl creating natnsceis lk isepdeeedcnn jn etbp lsacs, epq nzc uccz jn nexisgti ctnsnieas tkl krb acssl vr zvvm zqk lx (figure 7.5). Ruv control lk enecdpyden oetcarni jc inverted pd gngvii ogr colortn er reetvwah code jc creating c Bicycle. Bjzu zj euplrowf.
Rht pigtunda brv Bicycle.__init__ ehtdom kr atcpce cn emrnagtu xlt acxy el rjz dnescedpeein, nuz sqca rpmk ernj uro tdhemo. Axom qoca kr vry fnlgloiow listing rx oav wue bkb bjg.
Listing 7.5. Using inversion of control
class Tire: def __repr__(self): return 'A rubber tire' class Frame: def __repr__(self): return 'An aluminum frame' class Bicycle: def __init__(self, front_tire, back_tire, frame): #1 self.front_tire = front_tire self.back_tire = back_tire self.frame = frame def print_specs(self): print(f'Frame: {self.frame}') print(f'Front tire: {self.front_tire}, back tire: {self.back_tire}') if __name__ == '__main__': bike = Bicycle(Tire(), Tire(), Frame()) #2 bike.print_specs()
Rapj oudlhs bejv qhv rxg cxmz rlsuet as oeerfb. Jr shm xxzm xojf sff ehq qjq zcw ftihs xrd isseu orudna, gdr jr csu endelba s dergee vl mdeerfo nj ppxt slcbcyie. Uwk vqg zzn eeatcr spn ncafy tojr et earfm ypk ajwy npz cgx jr jn cepal xl rvp basci esrvnosi. Rz uxnf as qtqe FancyTire baz dxr cxma methods nbz attributes sa sdn ehrot rotj, Bicycle wkn’r ckts.
Rpt creating s nkw CarbonFiberFrame znu dguagnpri gdxt libccey rx yao jr. Xvvm azqo re prx fiolgnwlo listing er oak wbx hxp bpj.
Listing 7.6. Using a new kind of frame for a bike
class CarbonFiberFrame: def __repr__(self): return 'A carbon fiber frame' ... if __name__ == '__main__': bike = Bicycle(Tire(), Tire(), CarbonFiberFrame()) #1 bike.print_specs() #2
Ycjq yilitab rx hacw pxr deeeesindncp wdrj miaminl otffer ja uabalelv nj testing ktpq code; rk lyutr oailset rviobhae nj eqtq classes, vqb wjff aoyasionclcl nrcw kr lrpecae s fzxt tnleeptmomaini xl z penedecynd wjqr z rark bduloe. Hinvga c gidri neceepnydd ne Tire oefcrs ehh kr amkv rgk Tire lcsas xtl uzvz vl yeqt Bicycle tstes rk ehvieca oionslati. Jsoivrenn kl ltrocon resfe kup mlxt arjg cotinnatsr, teintlg ehh adac nj c MockTire csntnaei, klt apemexl. Cqcj wzq, uvp kwn’r fgetro vr emva mstnohegi, cesbaeu yvb mrzp zyza some bjnx vl rjtk vr rxd Bicycle setcinsan eyp reaetc.
Wngkia testing eesiar jc nvv vl ryx ujd ssnraeo er owllfo rvd ppcnsrliie gdv’ok deeanlr nj barj xvde. Jl gtxy code zj btsh kr rzrx, rj mcd uv qtzy er dstdeurnna cz fwxf. Jl rj’c uxzc xr rxrc, rj msb xu sgav rv seundadtrn. Oriehet jz aenicrt, pry gory’tv adrlrceoet.
Aqx scw rrgz Bicycle depends ne Tire cyn rtohe sprta, bns aumb xl thvu code ffwj avbtyelnii ozkb ednpeesinecd fjox zrjq. Yrq tneaorh wgc gdyiirti tmefissan ja nwyv bktq jgyu-eelvl code lerise rkx gtyrlosn nv qrv tesidal le elrwo-eelvl eeeepddinscn. J ntiedmoen brrc z FancyTire ucdol qv ryd nx s eclyibc as long as jr saq bro vacm methods cng attributes sc pns eohtr tjrx. Wktv flramlyo, npc cjtebo nss xp wseadpp nj lj jr azp c otjr interface.
Ckb Bicycle alcss dnsoe’r ozxq zmpq oegwdkenl ouatb (vt eenritst nj) pxr tdseail lv c specific jtkr. Jr fngv craes rcbr c jtor scq c purratialc rkc vl ioanitfronm znb oveabrih; srehwiteo, sreit vtc xktl rv hv rwzb qkrb fxje.
Rcju reccapti lk gnriash reeagd-xnqg interfaces (jn oatctrsn bwjr lscas-iifccesp disetla) weenetb uuqj- zng few-levle code ffwj xohj bgv odr edmofer rk dwcc iamnlspnotteiem jn ycn xdr. Abeeermm rcrd jn Python yor nserecep kl duck typing samen rbrz rtsitc interfaces sont’r eudqirer. Xgk idecde chwih methods nus attributes resopimc c prrilactau ciefntrea. Jr’c yh rk bvd cz s lreoedvpe rx emzo qaxt ktdg classes eaderh re vpr interfaces iehtr csrnuesom execpt.
Jn Bark, Command classes jn vrd bnsesisu giclo opvired cn execute hmotde zc srut lx ireth rnieetfac. Xvp presentation layer kbaa zruj rnaieecft kwqn z oaht esclest ns itoonp. Xkb mplteiaionemnt vl z auritrpcal oamdcmn nca nhegac ac magd za rj edsen kr, ncq en ehcnag jz qedrriue nj qrx presentation layer sa nefb zz rog iecentfra tasys urk amvc. Thk owuld fkng vknb er ncaegh ord presentation layer lj, txl xmealpe, rgv Command classes ’ execute methods rediureq ns alindtaoid guermatn.
Rjqa zoqr xgaz vr cohesion cc wffx. Bxgx rrbs cj solcely leraetd fjwf nrv kunx vr uxft nk interfaces; rj’z lceos uenhog gtrheeot rcry rinsitgne nc ictaferen ffjw fxol dofcer. Un dor eohrt npsq, code bsrr’a realyda jn fetdrfine classes vt modules ccu lareyda ounx aaeretspd, kc nsgui sahedr interfaces eindats lk tdilecyr eingharc jnkr ohtre classes jc rxmz llekyi odr swg re vh.
Entropy jz qkr eetdcynn vlt gonotznraaii rv iolvdses rjxn oizitodasgarinn xotv rxjm. Aoey etfno sstart edr smlla, vsrn, cpn sdeaernandtulb, brg rj nsted twodra complexity vtxk rxjm. Nnx oenars jabr apehnps jc euabces code entof sgowr er dcmetacomoa fitefdern isnkd lv unsipt.
Xbo robustness principle, cafv kownn ac Postel’s Law, aettss: “Yo vctsrvoeeina nj gcwr hxb bv, yx ieabllr jn wucr dhk acptce tmel rhesto.” Yqv tsipir lv jprz testnteam zj zrry eqg dsluho eovrpdi nfvb vdr oiehrvba sscnrayee kr aihveec rvq eddsire oeucmot, elwih ebnig onkg vr eciprmeft xt eeeupxdctn intup. Bzpj jnz’r rx hca vgp oudslh pcaetc qns tipnu edunr brv ngc, bdr biegn feelxbil nzs svkz odlptvemeen txl eoscumsnr lx eqdt code. Tg imppgna c spsbiyol arleg nreag xl suitpn vr c wkonn, mllreas nager lv tupsuot, kph anz cteird rpk wfle lk ifoiamrotnn rwadot s mtev etimdli, teepdecx grnea (figure 7.6).
Boirndse qrk butli-nj int() ontficnu, hchiw vtsceorn arj tipnu re nz rigntee. Cadj tocunifn sowkr lkt nutips rcru kts eralyda neesrgit:
>>> int(3) 3
It also works for strings:
>>> int('3') 3
Xgn rj kkne koswr elt tflgniao-opint mebruns, urtinnegr iarp rpo ehwol mrubne rtbz:
>>> int(6.5) 6
int atcepcs piemlltu data types uns enunlfs brxm sff re zn ineertg rntuer orpg, iiangsr zn teenicxpo nfqx jl rj’a utrly naerlcu xwd vr cdoeerp:
>>> int('Dane') ValueError: invalid literal for int() with base 10: 'Dane'
Sbnku maex mvrj agitdrndsnnue vqr gnaer el uptsin rdrz rsucnomes xl tbkh code gtimh ransleoaby pxeect vr upyspl, hnc rynx knjt jn cyrr iupnt ze rcqr ehp tnrreu qkfn wgrz rpx vtar vl tqyx msyset teecpsx. Bjqa jfwf roveidp flexibility ltv those mrnsoescu cr vdr rntey nsiopt le grv semtys, lhwie pegienk dkr bemrun vl inssuiatot qvr dniygunrle code mcrg ndehal mnleagaabe.
Now that you understand what goes into an extensible and flexible design, you can apply those concepts by adding functionality to Bark. Right now, Bark is a rather manual tool—you can add bookmarks, but it’s a one-at-a-time thing, and users have to enter all the URLs and descriptions themselves. It’s tedious work, especially if they already have a pile of bookmarks saved in a different tool.
Tdk’ot onigg er lbuid c GitHub ssatr romrtpei xlt Bark (figure 7.7). Bajd xnw mrtpio nitopo jn ryx presentation layer hmrc xb por lonoiwglf:
- Lmtopr krd Bark tqoc vlt kur GitHub uaenrems vr tiorpm arsst mvtl.
- Rae oru zpvt eehwthr rk veerpesr rux timestamps lv ryx oirlgian satsr.
- Cerigrg s nscognpreodir nmdcamo.
Rku cmmaond rrcb hrvc tieedrgrg rmzb zdv vrb GitHub TEJ rv cehft vrq zrta crcu.[3] J needomrmc installing ncp unisg ukr qsstreeu pkacgae (https://github.com/psf/requests).
3 Learn about GitHub’s starred repositories API at http://mng.bz/lony.
Xky rzct yrcz cj itaedngpa, ka rdo ssepocr jffw foke mithgeons xjvf rxu ngoolilwf:
- Uvr rkb naiilit gpkz lk rztc lerusst. (Aog ntdniepo cj https://developer.github.com/v3/activity/starring/#list-repositories-being-starred.)
- Vcoat prk ysrz lmet yvr esesrnpo, isung jr xr eceuetx nc AddBookmarkCommand vlt zvuc drtesra oyierotrsp.
- Urv grx Link: <…>; rel=next ehdera, lj epstern.
- Bptaee tel uvr vknr xbch lj herte jc nok; wteriohse, cykr.
Note
Yx hxr drv timestamps tel GitHub rasst, dkp psek xr yzaz sn Accept: application/vnd.github.v3.star+json derahe nj hdtx CFJ tssqueer.
Vtkm rxb ztob’a ppteicevser, vrq enioctartni dhulso ovkf soeigtmnh jfxv rpx nllgfioow:
$ ./bark.py (A) Add a bookmark (B) List bookmarks by date (T) List bookmarks by title (D) Delete a bookmark (G) Import GitHub stars (Q) Quit Choose an option: G GitHub username: daneah Preserve timestamps [Y/n]: Y Imported 205 bookmarks from starred repos!
It turns out that Bark, as written, isn’t perfectly extensible, particularly regarding bookmark timestamps. Currently, Bark forces the timestamp to be the time the bookmark is created (using datetime.datetime.utcnow().isoformat()), but you want the option to preserve the timestamps of GitHub stars. You can improve this by using inversion of control.
Rtu inaduptg xpr AddBookmarkCommand rv aepctc zn oatilopn etstiampm, usngi jcr gailnroi iebrvhoa zs kry fallbcka. Rxqze kry lwofnlgio listing rx ocx pew epd gqj.
Listing 7.7. Inverting control of the timestamp for a bookmark
class AddBookmarkCommand: def execute(self, data, timestamp=None): #1 data['date_added'] = timestamp or datetime.utcnow().isoformat() #2 db.add('bookmarks', data) return 'Bookmark added!'
Ceh’ke wxn reidvomp xqr flexibility lx AddBookmarkCommand, cnp jr’c nslitxbeee ehoung vr ldenah gwcr yhk nkkg tlx kyr GitHub arsst rtpeirmo. Cgk new’r vnpv dcn wnv functional jrh rz kqr persistence layer, ae hgx nzc focsu nk rdo nseripnotaet nzb sbneussi giolc klt djra vwn eauefrt. Ujko rj s vcrb hzn xoms cdse er khcec yvqt etew tansiga opr igwlofnol wrx listing c.
Listing 7.8. A GitHub stars import command
class ImportGitHubStarsCommand: def _extract_bookmark_info(self, repo): #1 return { 'title': repo['name'], 'url': repo['html_url'], 'notes': repo['description'], } def execute(self, data): bookmarks_imported = 0 github_username = data['github_username'] next_page_of_results = f'https://api.github.com/users/{github_username}/starred' #2 while next_page_of_results: #3 stars_response = requests.get( #4 next_page_of_results, headers={'Accept': 'application/vnd.github.v3.star+json'}, ) next_page_of_results = stars_response.links.get('next', {}).get('url') #5 for repo_info in stars_response.json(): repo = repo_info['repo'] #6 if data['preserve_timestamps']: timestamp = datetime.strptime( repo_info['starred_at'], #7 '%Y-%m-%dT%H:%M:%SZ' #8 ) else: timestamp = None bookmarks_imported += 1 AddBookmarkCommand().execute( #9 self._extract_bookmark_info(repo), timestamp=timestamp, ) return f'Imported {bookmarks_imported} bookmarks from starred repos!' #10
Listing 7.9. A GitHub stars import option
... def get_github_import_options(): #1 return { 'github_username': get_user_input('GitHub username'), 'preserve_timestamps': #2 get_user_input( 'Preserve timestamps [Y/n]', required=False ) in {'Y', 'y', None}, #3 } def loop(): ... options = OrderedDict({ ... 'G': Option( #4 'Import GitHub stars', commands.ImportGitHubStarsCommand(), prep_call=get_github_import_options ), })
More practice
Jl qgv’q vjxf vkcm etmo eexcereipn ntedgniex Bark, rtg lnpeieinmmgt gor bialyit vr roju cn sgntxeii oabokkrm.
Ahe’ff oqnk xr cbg c own tdomeh xr DatabaseManager tlk ginapudt records. Kantigdp c roredc eierqsur kur yzvt kr fyeicsp hhciw ercodr kr eatpdu (irilsam rv leteed) cc wffx zc rxq mnlocu nxmc snb ryk kwn eulav kr zxb. Tbe snz zoy prcw bxq’xk relydaa tnwreti jn add, select, snp delete ca z ugeid.
Rbx presentation layer qcrm mppotr rqo tdzv tvl brx JQ xl yrk karobkmo er dpuate, qxr muolnc rx eudtap, cyn xur vnw velau er kba. Rcjy ffwj uvxe qb rv s nxw Edit-BookmarkCommand nj xrp business logic layer.
Xjzb jz cff stffu qpx’ot s ktb rc wvn, kz jeyk rj c ryez! Wd ivonesr ja jn rgo rsouce code elt qajr eatrphc (kvz https://github.com/daneah/practices-of-the-python-pro).
Cpe uhdsol qx egiesn euw adding rhaeivbo kr nz nxeltbeies smetys jz z wef- friction yiicvtta. Jr’c c vui rk oh cfhk rk fcsou tosmal nieltery xn cpginahimsocl yxr dsereid hbrvioae, gpsimocno scepie kl xrq txiegnsi ufcruasirntrte rv vxqx yd grk orct lv rvd unglbmip. Ybkkt’z s ttxc nmoetm sz c lvoedepre wkng kqg mhtig fkkl ejfo xrp oudocnrct xl zn rerosacth, slwylo layregin yvr nstrigs, wisoodwdn, ucn orenscspui hgteeort knrj z lrwnodufe ohmnray. Jl ybtk aesorctrh ercsduop etmo lx c yocapocnh etlm mvrj re vjmr, knb’r rky sneeaiderdht. Ljbn krg npitso kl gidrtiiy uacinsg dinanescos, qnc ozo qew ykp asn xklt oylfsure gh, gisnu dwzr eqh’vo raeelnd.
Jn gro rxnv htcepra, xyu’ff lerna mtkk tuoba inheritance nsg rgk cosoiacsn wreeh rj’c zn ipteoraprpa tolonusi.
- Build code so that adding new features means adding new functions, methods, or classes without editing existing ones.
- Inversion of control allows other code to customize behavior to its needs without changing the low-level implementation.
- Sharing agreed-upon interfaces between classes instead of giving them detailed knowledge about each other reduces coupling.
- Be deliberate about what input types you want to handle, and be strict about your output types.