object calisthenics (pycon sk 2017)
TRANSCRIPT
ObjectCalisthenics9stepstobetterOOcode
Agenda
Learnhowtomakeourcodemore:
readablereusabletestablemaintainable
Raiseyouhandifyouknowoneofthefollowing:
DRYKISSSOLIDYAGNIZenofPython
Calisthenics
Cal•is•then•ics-/ˌkaləsˈTHeniks/
"Calisthenicsareexercisesconsistingofavarietyofgrossmotormovements;often
rhythmicalandgenerallywithoutequipmentorapparatus."
Wikipedia
ObjectCalisthenics
JeffBay
WrittenforJava
Whybother?
Codeisreadmorethanit'swritten
Authorunknown
Youneedtowritecodethatminimizesthetimeitwould
takesomeoneelsetounderstandit-evenifthat
someoneelseisyou
ArtofReadableCodebyDustinBoswell,TrevorFoucher
Rule#1
Onlyonelevelofindentationpermethod
classBoard(object):def__init__(self,data):#Level0self.buf=""foriinrange(10):#Level1forjinrange(10):#Level2self.buf+=data[i][j]
classBoard(object):def__init__(self,data):self.buf=""self.collect_rows(data)defcollect_rows(self,data):foriinrange(10):self.collect_row(data[i])defcollect_row(self,row):forjinrange(10):self.buf+=row[j]
products=self.get_products_by_user(...)ifproductsisNone:products=self.get_products_by_media(...)ifproductsisNone:products=self.get_products_by_domain(...)ifproductsisNone:products=self.get_any_products(...):ifproductsisNone:raiseException('Accessdenied')else:...else:...else:...else:...
Chainofcommand
Benefits
SingleresponsibilityBetternamingShortermethodsReusablemethods
Rule#2
Donotuseelsekeyword
ifoptions.get_categories()isNone:...eliflen(options.get_categories())==1:...elifSPECIAL_CATEGORYinoptions.get_categories():...elifoptions.get_categories()andoptions.get_query():...elifoptions.get_content_type():...
deflogin(self,request):ifrequest.user.is_authenticated():returnredirect("homepage")else:messages.add_message(request,messages.INFO,'Badcredentials')returnredirect("login")
deflogin(self,request):ifrequest.user.is_authenticated():returnredirect("homepage")
messages.add_message(request,messages.INFO,'Badcredentials')returnredirect("login")
deffunction(param):ifparamisnotNone:value=paramelse:value="default"
returnvalue
deffunction(param):value="default"ifparamisnotNone:value=param
returnvalue
Extractcode
Defaultvalue
Polymorphism
Strategypattern
Statepattern
Examples
https://github.com/gennad/Design-Patterns-in-Python
https://www.quora.com/Is-it-true-that-a-good-programmer-uses-fewer-if-conditions-than-an-amateur
BenefitsAvoidscodeduplicationLowercomplexityReadability
Rule#3
Wrapprimitivetypesifithasbehaviour
ValueObjectinDDD
classValidator(object):defcheck_date(self,year,month,day):pass
#10thofDecemberor12thofOctober?validator=Validator()validator.check_date(2016,10,12)
classValidator(object):defcheck_date(self,year:Year,month:Month,day:Day)->bool:pass
#Functioncallleavesnodoubt.validator.check_date(Year(2016),Month(10),Day(12))
BenefitsEncapsulationTypehintingAttractssimilarbehaviour
Rule#4
Onlyonedotperline
OK:Fluentinterface
classPoem(object):def__init__(self,content):self.content=content
defindent(self,spaces):self.content=""*spaces+self.content
returnself
defsuffix(self,content):self.content=self.content+"-"+content
returnself
Poem("RoadNotTravelled").indent(4)\.suffix("RobertFrost")\.content
NotOK:getterchain
classCartService(object):defget_token(self):token=self.get_service('auth')\.auth_user('user','password')\.get_result()\.get_token()
returntoken
#1.WhatifNoneisreturnedinsteadofobject?#2.Howaboutexceptionshandling?
classField(object):def__init__(self):self.current=Piece()
classPiece(object):def__init__(self):self.representation=""
classBoard(object):defboard_representation(self,board):buf=''forfieldinboard:buf+=field.current.representation
returnbuf
classField(object):def__init__(self):self.current=Piece()defadd_to(self,buffer):returnself.current.add_to(buffer)
classPiece(object):def__init__(self):self.representation=""defadd_to(self,buffer):returnbuffer+self.representation
classBoard(object):defboard_representation(self,board):buf=''forfieldinboard:buf=field.add_to(buf)
returnbuf
BenefitsEncapsulationDemeter'slawOpen/ClosedPrinciple
Rule#5
Donotabbreviate
Whyabbreviate?
Toomanyresponsibilities
Nametoolong?
Split&extract
Duplicatedcode?
Refactor!
classOrder(object):defship_order(self):pass
order=Order()order.ship_order()
//vs
classOrder(object):defship(self):pass
order=Order()order.ship()
acc=0//accumulator?accuracy?
pos=100//position?pointofsale?positive?
auth=None//authentication?authorization?both?
BenefitsClearintentionsIndicateunderlyingproblems
Rule#6
Keepyourclassessmall
Whatissmallclass?15-20linespermethod50linesperclass10classespermodule
BenefitsSingleResponsibilitySmallermodules
Rule#7
Nomorethan2instancevariableperclass
Classshouldhandlesinglevariablestate
Insomecasesitmightbetwovariables
classCartService(object):def__init__(self):self.logger=Logger()self.cart=CartCollection()self.translationService=TranslationService()self.auth_service=AuthService()self.user_service=UserService()
BenefitsHighcohesionEncapsulationFewerdependencies
Rule#8
Firstclasscollections
collectionsmodule
BenefitsSingleResponsibility
Rule#9
Donotusesetters/getters
Accessorsarefine
Don'tmakedecisionsoutsideofclass
Letclassdoit'sjob
Tell,don'task
classGame(object):def__init__(self):self.score=0
defset_score(self,score):self.score=score
defget_score(self):returnself.score
#UsageENEMY_DESTROYED_SCORE=10game=Game()game.set_score(game.get_score()+ENEMY_DESTROYED_SCORE)
classGame(object):def__init__(self):self.score=0defadd_score(self,score):self.score+=score
#UsageENEMY_DESTROYED_SCORE=10game=Game()game.add_score(ENEMY_DESTROYED_SCORE)
BenefitsOpen/ClosedPrinciple
Catch'emall!
Catch'emall!1. Onlyonelevelofindentationpermethod,2. Donotuseelsekeyword,3. Wrapprimitivetypesifithasbehavior,4. Onlyonedotperline,5. Don’tabbreviate,6. Keepyourentitiessmall,7. Nomorethantwoinstancevariableperclass,8. FirstClassCollections,9. Donotuseaccessors
Catch'emall!1. Onlyonelevelofindentationpermethod,2. Donotuseelsekeyword,3. Wrapprimitivetypesifithasbehavior,4. Onlyonedotperline,5. Don’tabbreviate,6. Keepyourentitiessmall,7. Nomorethantwoinstancevariableperclass,8. FirstClassCollections,9. Donotuseaccessors10. ???11. PROFIT!
Homework
Createnewprojectupto1000lineslong
Applypresentedrulesasstrictlyaspossible
Drawyourownconculsions
Customizetheserules
Finalthoughts
Thesearenotbestpractices
Thesearejustguidelines
Usewithcaution!
Questions?
Links
https://en.wikipedia.org/wiki/Calisthenicshttps://www.cs.helsinki.fi/u/luontola/tdd-2009/ext/ObjectCalisthenics.pdfhttps://pragprog.com/book/twa/thoughtworks-anthologyhttps://github.com/gennad/Design-Patterns-in-Pythonhttps://en.wikipedia.org/wiki/Law_of_Demeterhttps://www.quora.com/Is-it-true-that-a-good-programmer-uses-fewer-if-conditions-than-an-amateur
Thankyou!