Exhibition of Atrocity

2,024 views

Published on

Slides from my PyCon 2011 talk, "Exhibition of Atrocity," a confessional of my sins against the Python programming language.

Abstract: http://us.pycon.org/2011/schedule/presentations/138/

Video: http://www.pycon.tv/#/video/49

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
2,024
On SlideShare
0
From Embeds
0
Number of Embeds
590
Actions
Shares
0
Downloads
28
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • Hi, I’m Mike. I’ve been working with Python for 11 years and one week, and during that time [SWITCH]\n
  • ...I’ve had THIS experience pretty regularly. I’ve come to think that one of the best ways to know what good code looks like is to know what it DOESN’T look like. So today we’re going to take a tour of some of my code sins and try to save you some of that toe-stubbing, face-palming pain.\n
  • I write most of my code for work; for this talk, most of the examples I’m going to show have been abstracted down from actual code or distilled from memory of past habits so that I can have permission to use them; hopefully this helps us focus on actual insights rather than wasting your time with reams of gnarly source code. I’ve got a lot of stuff to talk about and I’m going to move fast to get through it.\n
  • When I started with Python, our shop was religious about Hungarian notation\n
  • That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
  • That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
  • That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
  • That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
  • That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
  • That’s where you prefix your variable names to indicate what sort of things they are. Meant to be a guidepost for the future, hint at intentions, set expectations. At first, this seems quite reasonable.\n
  • But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
  • But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
  • But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
  • But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
  • But this quickly leads to silliness - a list of dictionaries, useless “hey look an object” notation, abbreviations, things that don’t know what they want, and frightening ambiguity\n
  • From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
  • From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
  • From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
  • From there it’s a short trip to outright lies -- a number that isn’t a number, fake false values in booleans, and, if something goes wrong, AttributeErrors when you get a None where you don’t expect to.\n
  • It was also a time before I really knew or cared about community standards and good style\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • I picked up bad habits from the existing codebase and perpetuated them for much longer than I should have. [quick remarks about each]\n
  • There’s an appealing foolishness to lining things up; it may look nice at first but you’re quickly locked into a hell-spiral of adjusting the interior whitespace as code changes, and annoying artifacts are inevitably left behind.\n
  • There’s an appealing foolishness to lining things up; it may look nice at first but you’re quickly locked into a hell-spiral of adjusting the interior whitespace as code changes, and annoying artifacts are inevitably left behind.\n
  • Short variable names mean you can fit more code into an 80-character line, so they must be good, right?\n
  • Wrong. Inscrutable variable names lead to code that’s hard to read and that people are afraid to clean up.\n
  • I can only take credit for finding this one--check out how defining everything as single characters lets us write lines like this:\n
  • I’ve become increasingly allergic to lambdas. Even when they’re simple, they’re hard to read...\n
  • And they’ll get complicated in a hurry. (Sadly, I couldn’t find the triple-lambda that I remember writing many moons ago...)\n
  • And they’ll get complicated in a hurry. (Sadly, I couldn’t find the triple-lambda that I remember writing many moons ago...)\n
  • Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
  • Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
  • Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
  • Worse, the desire for brevity drives us to use inscrutable single-character variable names!\nIn the interest of optimizing for our fellow humans, give your functions descriptive names instead of packing as much cleverness as possible into a single line. Your functions deserve it, and your colleagues deserve it.\n
  • List comprehensions are really powerful, and beautiful until you start filling them full of crap.\n
  • Nesting list comprehensions adds significant cognitive load for most mortals. First one’s not too bad; second one starts to scare the junior devs who aren’t friends with zip yet; but this last one is just freaky.\n
  • Nesting list comprehensions adds significant cognitive load for most mortals. First one’s not too bad; second one starts to scare the junior devs who aren’t friends with zip yet; but this last one is just freaky.\n
  • Nesting list comprehensions adds significant cognitive load for most mortals. First one’s not too bad; second one starts to scare the junior devs who aren’t friends with zip yet; but this last one is just freaky.\n
  • This is taken from a personal project that scrapes my LiveJournal friends page and converts it into an RSS feed. Minus some formatting issues, it’s not terribly unclean, but so much space gets taken by the setup of RSSItem that it’s easy to lose track of the list comprehension itself.\n
  • Doesn’t this look cleaner?\n
  • This deadly pattern emerges when you aim to provide a “one stop shop” that has to deal with a lot of special cases.\n
  • For example, swapping out functionality based on what site we’re serving. Once you start tacking on a few elifs, it’s hard to stop!\n
  • Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
  • Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
  • Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
  • Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
  • Pull the insides of the if/elif block out into separate functions; build a map based on some key, then just choose the key, grab the function, and call it. But this can get ugly too as determining the key can be a lot of work.\n
  • So why not externalize it entirely -- let it get chosen by the caller, who may know better than we do about those deciding factors.\n
  • And then it’s just a quick step into Dependency Injection, where our code gets provided with what it needs.\n
  • You can even use a DI framework like snake-guice to help wire up your objects.\n
  • You can even use a DI framework like snake-guice to help wire up your objects.\n
  • You can even use a DI framework like snake-guice to help wire up your objects.\n
  • \n
  • I didn’t actually do this one, as I had learned the lesson the hard way; instead I let this happen to a coworker who was new to Python by not making it part of his early onboarding to the language. Our app was only supposed to let users set a max of 4 reminders for an event, but...\n
  • we’d keep accumulating more and more data in that list as the process handled more and more requests. Periodically we’d restart the app server and the problem would vanish, only to build again.\n
  • we’d keep accumulating more and more data in that list as the process handled more and more requests. Periodically we’d restart the app server and the problem would vanish, only to build again.\n
  • \n
  • \n
  • There are various ways of doing this; here’s one.\n
  • There are various ways of doing this; here’s one.\n
  • There are various ways of doing this; here’s one.\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • In between Perl and Python, I was briefly a Java guy, and while my exposure was brief, the effects were lasting.\n
  • I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
  • I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
  • I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
  • I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
  • I wrote an invitations app years ago, all of the model classes were very protective of their internals, but without much benefit. Same basic get/set pain repeated ~40 times in that class. And the calling syntax gets cumbersome and ugly.\n
  • Much nicer to just access attributes directly and use modern property syntax if we really need special logic\n
  • Much nicer to just access attributes directly and use modern property syntax if we really need special logic\n
  • Much nicer to just access attributes directly and use modern property syntax if we really need special logic\n
  • That invite stuff that had all those getters and setters also had wicked __init__s that would take 40 or more parameters and then call all those getters and setters. So in my next big project I thought I’d learn from that and do some magic.\n
  • Was building a backend for a reminder system that was going to use Data Transfer Objects to speak XMLRPC with our web app front end, and needed to make sure that all the data attributes made it across the wire...\n
  • So with a little __new__ magic, we transform all of the class attributes into instance attributes so that xmlrpclib will include our Nones\n
  • So with a little __new__ magic, we transform all of the class attributes into instance attributes so that xmlrpclib will include our Nones\n
  • So with a little __new__ magic, we transform all of the class attributes into instance attributes so that xmlrpclib will include our Nones\n
  • Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
  • Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
  • Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
  • Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
  • Then, remembering those brutal inits, it seems reasonable to automatically set everything that got passed in our keyword args--we just saved a ton of typing, right? And maybe only allow it to be set if we declared up front that it’s something our DTO *should* be getting.\n
  • Eventually I wanted to be able to declare mutables -- like lists and dictionaries -- in my class and have fresh, unshared copies of them for each instance that’s created, and thus was born MutantDataObject.\n
  • This init-less convenience becomes quite popular and used in a number of systems. Then one day we get a nasty surprise from a system that uses them heavily--it’s dog slow.\n
  • \n
  • The lookup and copying of mutables blows MutantDataObject’s startup time through the roof. So how do we fix it?\n
  • Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
  • Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
  • Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
  • Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
  • Our replacement DataObject is now based on a metaclass that knows how to find and copy those mutables more efficiently, and it also takes advantage of iterators\n
  • And here’s what the metaclass looks like--don’t look at it too hard now, I’ll have the slides up soon.\n
  • The important thing is that it saves a lot of time!\n
  • A “god object” or “god method” is one that has accumulated too many responsibilities. It may start out subtle at first, but they’re easy to recognize before long...\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Favor small functions and small classes with as few responsibilities as possible. Strive to do no work in the init. Break up the wanna-be gods before they get out of hand.\n
  • This is one of my deepest regrets.\n
  • It started so simply, as a favor to another developer, who needed easy access to some objects and didn’t want to pass them around everywhere... Basically it’s a module with module-level globals that get repopulated by the app server on every request\n
  • If you import it, you can talk to those globals. You can do this at any level, from the lofty heights that first see the request, to the deepest, darkest depths.\n
  • Try not to do this.\n
  • Try not to do this.\n
  • Try not to do this.\n
  • Try not to do this.\n
  • Try not to do this.\n
  • Having easy access to all that awesome global state also encourages you to break the Law of Demeter, which basically says that you should only interact with the things you know about and not reach through to “friends of friends” or “strangers”. But it’s damn convenient!\n
  • Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
  • Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
  • Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
  • Here are just a few of countless examples of my crimes -- reaching across multiple objects to call a method, through dictionaries, into single-underscore “internals”, into the contents of a list...\n
  • \n
  • The Diaper Pattern is a popular term in my office for the antipattern of catching and silencing exceptions\n
  • A lot of us have young kids, so we naturally call this a “diaper” because it tries to catch all the crap. Unfortunately, it doesn’t always work so well, and we can have “blowouts” -- if anything had gone wrong getting foo, our call to do_something_important will die!\n\nThe farther away the diaper is from the code that it breaks, the harder it is to find--in a large codebase, it can be buried quite a ways down.\n
  • A lot of us have young kids, so we naturally call this a “diaper” because it tries to catch all the crap. Unfortunately, it doesn’t always work so well, and we can have “blowouts” -- if anything had gone wrong getting foo, our call to do_something_important will die!\n\nThe farther away the diaper is from the code that it breaks, the harder it is to find--in a large codebase, it can be buried quite a ways down.\n
  • A lot of us have young kids, so we naturally call this a “diaper” because it tries to catch all the crap. Unfortunately, it doesn’t always work so well, and we can have “blowouts” -- if anything had gone wrong getting foo, our call to do_something_important will die!\n\nThe farther away the diaper is from the code that it breaks, the harder it is to find--in a large codebase, it can be buried quite a ways down.\n
  • We’re better off when exceptions bubble up, then at least you know where they came from. Errors, as they say, should not pass silently.\nOK to diaper at system boundaries - eg, around a web service or xmlrpc backend where you always want reasonable output\n
  • Coming into the home stretch now, let’s talk about some fun ways to make people angry. We’re going to meet a thing called DUCK PUNCHER.\n
  • If the test breaks, the teardown never happens, and whatever was “punched” stays punched. Maybe this hurts you immediately -- if “open” got punched, nose will die because it can’t get at the stack trace -- or maybe it hurts you thirty directories away when something that wasn’t changed starts failing.\n
  • The immediate fix is to make sure we have a try/finally block.\n\n
  • Really folks should just learn to use Mock's @patch decorator or context manager and not reinvent the wheel\n
  • \n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • Once upon a time in the bad old days of Python 1.4, we needed to log.\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Exhibition of Atrocity

    1. 1. Exhibition of Atrocity Mike Pirnat // PyCon 2011 @mpirnat
    2. 2. Disclaimer
    3. 3. Lies, Damn Lies, andHungarian Notation
    4. 4. Good Intentions
    5. 5. Good Intentions• strFirstName
    6. 6. Good Intentions• strFirstName• intYear
    7. 7. Good Intentions• strFirstName• intYear• blnSignedIn
    8. 8. Good Intentions• strFirstName• intYear• blnSignedIn• fltTaxRate
    9. 9. Good Intentions• strFirstName• intYear• blnSignedIn• fltTaxRate• lstProducts
    10. 10. Good Intentions• strFirstName• intYear• blnSignedIn• fltTaxRate• lstProducts• dctParams
    11. 11. Silliness
    12. 12. Silliness• lctResults ??
    13. 13. Silliness• lctResults ??• objMyReallyLongName ??
    14. 14. Silliness• lctResults ??• objMyReallyLongName ??• objMRLN ??
    15. 15. Silliness• lctResults ??• objMyReallyLongName ??• objMRLN ??• varValue ??
    16. 16. Silliness• lctResults ??• objMyReallyLongName ??• objMRLN ??• varValue ??• blnMagic ??
    17. 17. LIES
    18. 18. LIES• strCustomerNumber
    19. 19. LIES• strCustomerNumber• blnFoo = N
    20. 20. LIES• strCustomerNumber• blnFoo = N• ‘NoneType’ object has no attribute...
    21. 21. Crimes Against PEP-8
    22. 22. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    23. 23. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    24. 24. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    25. 25. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    26. 26. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    27. 27. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    28. 28. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    29. 29. class MyGiganticUglyClass(object): def iUsedToWriteJava(self,x,y = 42): blnTwoSpacesAreMoreEfficient = 1 while author.tragicallyConfused(): print "Three spaces FTW roflbbq!!1!" if (new_addition): four_spaces_are_best = True if (multipleAuthors or peopleDisagree): print "tabs! spaces are so mainstream" return ((pain) and (suffering))
    30. 30. from regrets import unfortunate_choicesclass AnotherBadHabit(object): short_name = foo much_longer_name = bar def __init__(self, x, y, z): self.x = x self.y = y self.z_is_a_silly_name = z self.came_later = 42 self.leftover = ‘timewaster’ self.dictionary = { foo : bar, bar : baz, baz : quux, }
    31. 31. from regrets import unfortunate_choicesclass AnotherBadHabit(object): short_name = foo much_longer_name = bar def __init__(self, x, y, z): self.x = x self.y = y self.z_is_a_silly_name = z self.came_later = 42 self.leftover = ‘timewaster’ self.dictionary = { foo : bar, bar : baz, baz : quux, }
    32. 32. from regrets import unfortunate_choicesclass AnotherBadHabit(object): short_name = foo much_longer_name = bar def __init__(self, x, y, z): self.x = x self.y = y self.z_is_a_silly_name = z self.came_later = 42 self.leftover = ‘timewaster’ self.dictionary = { foo : bar, bar : baz, baz : quux, }
    33. 33. A Twisty Maze ofSingle-CharacterVariables, All Alike
    34. 34. f.write(string.join(map(lambdax,y=self.__dicProfiles,z=strPy:"%0.3s %s:%s:(%s)" % (z,x,y[x][0],y[x][1]),self.__dicProfiles.keys()),n)+n)
    35. 35. #!/usr/bin/env pythonimport os,sysC=os.chdirS=os.systemM=os.mkdirJ=os.path.joinA=os.path.abspathD=os.path.dirnameE=os.path.existsW=sys.stdout.writeV=sys.argvX=sys.exitERR=lambda m:W(m+"n")PRNT=lambda m:W(m+"n")assert len(V)==2,"you must provide a sandbox name"SB=V[1]H=A(D(__file__))SBD=J(D(H),SB)C(SBD)PST=J(SBD,bin/paster)VAR=J(SBD,var)ETC=J(SBD,etc)S("mkdir -p "+VAR)PRNT("restarting "+SB)CMD=";".join([source %s%J(SBD,bin/activate),PST+" serve --daemon --pid-file=%s/sandbox.pid --log-file=%s/sandbox.log %s/sandbox.ini start"%(VAR,VAR,ETC)])PRNT(CMD)S(CMD)PRNT("All done!")X(0)
    36. 36. #!/usr/bin/env pythonimport os,sysC=os.chdirS=os.systemM=os.mkdirJ=os.path.joinA=os.path.abspathD=os.path.dirnameE=os.path.existsW=sys.stdout.writeV=sys.argvX=sys.exitERR=lambda m:W(m+"n")PRNT=lambda m:W(m+"n")assert len(V)==2,"you must provide a sandbox name"SB=V[1]H=A(D(__file__))SBD=J(D(H),SB)C(SBD)PST=J(SBD,bin/paster)VAR=J(SBD,var)ETC=J(SBD,etc)S("mkdir -p "+VAR)PRNT("restarting "+SB)CMD=";".join([source %s%J(SBD,bin/activate),PST+" serve --daemon --pid-file=%s/sandbox.pid --log-file=%s/sandbox.log %s/sandbox.ini start"%(VAR,VAR,ETC)])PRNT(CMD)S(CMD)PRNT("All done!")X(0)
    37. 37. Lambdas, Lambdas Everywhere
    38. 38. lstRollOut = lstRollOut + filter(lambda x:x[-1] == 0, filter(lambda x: x != 0|0,lstMbrSrcCombo))
    39. 39. lstRollOut = lstRollOut + filter(lambda x:x[-1] == 0, filter(lambda x: x != 0|0,lstMbrSrcCombo))if not filter(lambda lst, sm=sm: sm inlst, map(lambda x, dicA=dicA: dicA.get(x,[]), lstAttribute)):
    40. 40. _make_keys = lambda cc, pm:tuple(map(lambda m, c=cc: ("%s.%s" % (c,m), m), pm))
    41. 41. _make_keys = lambda cc, pm:tuple(map(lambda m, c=cc: ("%s.%s" % (c,m), m), pm))return [map(lambda l: l[0],lstResults),map(lambda l: l[1],lstResults)]
    42. 42. _make_keys = lambda cc, pm:tuple(map(lambda m, c=cc: ("%s.%s" % (c,m), m), pm))return [map(lambda l: l[0],lstResults),map(lambda l: l[1],lstResults)]sum = lambda lst: lst and reduce(lambda x,y: x + y, lst) or 0
    43. 43. _make_keys = lambda cc, pm:tuple(map(lambda m, c=cc: ("%s.%s" % (c,m), m), pm))return [map(lambda l: l[0],lstResults),map(lambda l: l[1],lstResults)]sum = lambda lst: lst and reduce(lambda x,y: x + y, lst) or 0assert reduce(lambda x,y: x or y, [z.id ==event.id for z in events])
    44. 44. The ListComprehension that Ate Cincinnati
    45. 45. lstCrumb = [y for y in [x.replace(",) for x inlstCrumb] if y]
    46. 46. lstCrumb = [y for y in [x.replace(",) for x inlstCrumb] if y]return [dict(x) for x in [zip(lstKeys,x) for x inlstValues]]
    47. 47. lstCrumb = [y for y in [x.replace(",) for x inlstCrumb] if y]return [dict(x) for x in [zip(lstKeys,x) for x inlstValues]]prop_list = [FilterProp(prop=P_EXCLUDED,data=_.join([i,j,k])) for i in prop_data[0] forj in prop_data[1] for k in prop_data[2]]
    48. 48. def feedparser_entries_to_RSSItems(entries): rss_items = [PyRSS2Gen.RSSItem( title = x.get(title,Untitled), link = x.link, description = x.summary, guid = x.link, pubDate = datetime.datetime( x.modified_parsed[0], x.modified_parsed[1], x.modified_parsed[2], x.modified_parsed[3], x.modified_parsed[4], x.modified_parsed[5])) for x in entries] return rss_items
    49. 49. def feedparser_entries_to_RSSItems(entries): rss_items = [rss_item(entry) for entry in entries] return rss_items
    50. 50. The Beast with a Thousand Elifs
    51. 51. def do_awesome_stuff(): ... if site.isSiteOne(): ... elif site.isSiteTwo(): ... elif site.getSite() in [site3, site4]: ... elif site.isSiteFive(): ... else: ... ...
    52. 52. def strategy1(): ...def strategy2(): ...site_strategies = { site1: strategy1, site2: strategy2, ...}def do_awesome_stuff(): which_one = ... strategy = site_strategies[which_one] strategy()
    53. 53. def strategy1(): ...def strategy2(): ...site_strategies = { site1: strategy1, site2: strategy2, ...}def do_awesome_stuff(): which_one = ... strategy = site_strategies[which_one] strategy()
    54. 54. def strategy1(): ...def strategy2(): ...site_strategies = { site1: strategy1, site2: strategy2, ...}def do_awesome_stuff(): which_one = ... strategy = site_strategies[which_one] strategy()
    55. 55. def strategy1(): ...def strategy2(): ...site_strategies = { site1: strategy1, site2: strategy2, ...}def do_awesome_stuff(): which_one = ... strategy = site_strategies[which_one] strategy()
    56. 56. def do_awesome_stuff(strategy): ... strategy() ...
    57. 57. def do_awesome_stuff(strategy): ... strategy() ...
    58. 58. class Foo(object): def __init__(self, strategy): self.strategy = strategy def do_awesome_stuff(self): ... self.strategy() ...
    59. 59. class Foo(object): def __init__(self, strategy): self.strategy = strategy def do_awesome_stuff(self): ... self.strategy() ...
    60. 60. class Foo(object): @inject(strategy=IStrategy) def __init__(self, strategy): self.strategy = strategy def do_awesome_stuff(self): ... self.strategy() ...
    61. 61. class Foo(object): @inject(strategy=IStrategy) def __init__(self, strategy): self.strategy = strategy def do_awesome_stuff(self): ... self.strategy() ...
    62. 62. class Foo(object): @inject(strategy=IStrategy) def __init__(self, strategy): self.strategy = strategy def do_awesome_stuff(self): ... self.strategy() ...
    63. 63. The Malignant Menace of Mutable Keyword Argument Defaults
    64. 64. def set_reminders(self, event, reminders=[]): ...
    65. 65. def set_reminders(self, event, reminders=[]): ...
    66. 66. • Extra reminders came from other users requests...
    67. 67. • Extra reminders came from other users requests...• What if this was really sensitive data?
    68. 68. def set_reminders(self, event, reminders=None): reminders = reminders or [] ...
    69. 69. def set_reminders(self, event, reminders=None): reminders = reminders or [] ...
    70. 70. def set_reminders(self, event, reminders=None): reminders = reminders or [] ...
    71. 71. DCT_BRAND_REMINDERS = { SITE_X: [ HOLIDAYS: [Reminder(...), ...], OTHER: [Reminder(...), ...], CUSTOM: [Reminder(...), ...], ], ...}...class BrandWrangler(object): ... def get_default_reminders(self, brand): return DCT_BRAND_REMINDERS.get(brand, {})
    72. 72. DCT_BRAND_REMINDERS = { SITE_X: [ HOLIDAYS: [Reminder(...), ...], OTHER: [Reminder(...), ...], CUSTOM: [Reminder(...), ...], ], ...}...class BrandWrangler(object): ... def get_default_reminders(self, brand): return DCT_BRAND_REMINDERS.get(brand, {})
    73. 73. DCT_BRAND_REMINDERS = { SITE_X: [ HOLIDAYS: [Reminder(...), ...], OTHER: [Reminder(...), ...], CUSTOM: [Reminder(...), ...], ], ...}...class BrandWrangler(object): ... def get_default_reminders(self, brand): return DCT_BRAND_REMINDERS.get(brand, {})
    74. 74. DCT_BRAND_REMINDERS = { SITE_X: [ HOLIDAYS: [Reminder(...), ...], OTHER: [Reminder(...), ...], CUSTOM: [Reminder(...), ...], ], ...}...class BrandWrangler(object): ... def get_default_reminders(self, brand): return copy.deepcopy( DCT_BRAND_REMINDERS.get(brand, {}))
    75. 75. DCT_BRAND_REMINDERS = { SITE_X: [ HOLIDAYS: [Reminder(...), ...], OTHER: [Reminder(...), ...], CUSTOM: [Reminder(...), ...], ], ...}...class BrandWrangler(object): ... def get_default_reminders(self, brand): return copy.deepcopy( DCT_BRAND_REMINDERS.get(brand, {}))
    76. 76. Java PTSD:Getters and Setters
    77. 77. class InviteEvent(object): ... def getEventNumber(self): return self._intEventNumber ... def setEventNumber(self, x): self._intEventNumber = int(x) ...event.setEventNumber(10)print event.getEventNumber()
    78. 78. class InviteEvent(object): ... def getEventNumber(self): return self._intEventNumber ... def setEventNumber(self, x): self._intEventNumber = int(x) ...event.setEventNumber(10)print event.getEventNumber()
    79. 79. class InviteEvent(object): ... def getEventNumber(self): return self._intEventNumber ... def setEventNumber(self, x): self._intEventNumber = int(x) ...event.setEventNumber(10)print event.getEventNumber()
    80. 80. class InviteEvent(object): ... def getEventNumber(self): return self._intEventNumber ... def setEventNumber(self, x): self._intEventNumber = int(x) ...event.setEventNumber(10)print event.getEventNumber()
    81. 81. class InviteEvent(object): ... @property def event_number(self): return self._event_number @event_number.setter def _set_event_number(self, x): self._intEventNumber = int(x) ...event.event_number = 10print event.event_number
    82. 82. class InviteEvent(object): ... @property def event_number(self): return self._event_number @event_number.setter def _set_event_number(self, x): self._intEventNumber = int(x) ...event.event_number = 10print event.event_number
    83. 83. class InviteEvent(object): ... @property def event_number(self): return self._event_number @event_number.setter def _set_event_number(self, x): self._intEventNumber = int(x) ...event.event_number = 10print event.event_number
    84. 84. DTO Bondage
    85. 85. class Calendar(DataObject): calendar_id = None user_id = None label = None description = None
    86. 86. class DataObject(object): def __new__(cls, *args, **kwargs): obj = object.__new__(cls) for key, value in cls.__dict__.items(): if not callable(value) and not hasattr(value, __get__) and not key.startswith(_): setattr(obj, key, value) return obj
    87. 87. class DataObject(object): def __new__(cls, *args, **kwargs): obj = object.__new__(cls) for key, value in cls.__dict__.items(): if not callable(value) and not hasattr(value, __get__) and not key.startswith(_): setattr(obj, key, value) return obj
    88. 88. class DataObject(object): def __new__(cls, *args, **kwargs): obj = object.__new__(cls) for key, value in cls.__dict__.items(): if not callable(value) and not hasattr(value, __get__) and not key.startswith(_): setattr(obj, key, value) return obj
    89. 89. class StrictDataObject(DataObject): def __init__(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) else: raise AttributeError( key %r must be predefined at the class level % key)
    90. 90. class StrictDataObject(DataObject): def __init__(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) else: raise AttributeError( key %r must be predefined at the class level % key)
    91. 91. class StrictDataObject(DataObject): def __init__(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) else: raise AttributeError( key %r must be predefined at the class level % key)
    92. 92. class StrictDataObject(DataObject): def __init__(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) else: raise AttributeError( key %r must be predefined at the class level % key)
    93. 93. class MutantDataObject(StrictDataObject): def __new__(cls, *args, **kwargs): new_object = object.__new__(cls) for c in cls.__mro__[::-1]: update_friendly_dict = _get_update_friendly_dict_from_class_dictproxy(c.__dict__) new_object.update(update_friendly_dict) return new_object def __init__(self, **kwargs): deferred = {} clean = {} for key, value in kwargs.items(): if hasattr(self, key): clean[key] = value else: deferred[key] = value super(MutantDataObject, self).__init__(**clean) self.update(deferred) ...
    94. 94. time passes...
    95. 95. Time to instantiate 1000001.91.81.71.61.5 DataObject StrictDataObject
    96. 96. Time to instantiate 100000 1511.25 7.5 3.75 0 DataObject StrictDataObject MutantDataObject
    97. 97. class DataObject(object): _mutables = None __metaclass__ = DataObjectMeta def __new__(cls, **kwargs): obj = object.__new__(cls) for name, default_func in cls._mutables: if name not in kwargs: setattr(obj, name, default_func()) return obj def __init__(self, **kwargs): for name, value in kwargs.iteritems(): setattr(self, name, value)
    98. 98. class DataObject(object): _mutables = None __metaclass__ = DataObjectMeta def __new__(cls, **kwargs): obj = object.__new__(cls) for name, default_func in cls._mutables: if name not in kwargs: setattr(obj, name, default_func()) return obj def __init__(self, **kwargs): for name, value in kwargs.iteritems(): setattr(self, name, value)
    99. 99. class DataObject(object): _mutables = None __metaclass__ = DataObjectMeta def __new__(cls, **kwargs): obj = object.__new__(cls) for name, default_func in cls._mutables: if name not in kwargs: setattr(obj, name, default_func()) return obj def __init__(self, **kwargs): for name, value in kwargs.iteritems(): setattr(self, name, value)
    100. 100. class DataObject(object): _mutables = None __metaclass__ = DataObjectMeta def __new__(cls, **kwargs): obj = object.__new__(cls) for name, default_func in cls._mutables: if name not in kwargs: setattr(obj, name, default_func()) return obj def __init__(self, **kwargs): for name, value in kwargs.iteritems(): setattr(self, name, value)
    101. 101. class DataObjectMeta(type): def __new__(meta, classname, bases, classdict): mutables = find_mutables(classdict, bases) classdict[_mutables] = mutables return type.__new__(meta, classname, bases, classdict)def find_mutables(classdict, bases): def create_default(name, value): return name, lambda: copy.copy(value) found_mutables = set() mutables = set() for name, value in classdict.items(): if (not name.startswith(__) and not callable(value) and not hasattr(value, __get__) and isinstance(value, (list, dict))): if not name in found_mutables: found_mutables.add(name) mutables.add(create_default(name, value)) del classdict[name] for base in bases: if hasattr(base, _mutables): for name, value in base._mutables: if not name in found_mutables: found_mutables.add(name) mutables.add((name, value)) return frozenset(mutables)
    102. 102. Time to instantiate 100000 1511.25 7.5 3.75 0 DataObject StrictDataObject MutantDataObject Metaclass DataObject
    103. 103. God Objects, God Methods, andThe Mile-Long Club
    104. 104. class CardOrderPage(WebPage): ... ~2900 lines 69 methods __init__ ~85 lines _populateFromUPD ~200 lines _populateMisc ~200 lines _completeOrder ~300 lines _buildOrderDct ~300 lines _redirectTo ~200 lines
    105. 105. class MemberOrderPage(WebPage): ... ~2400 lines 58 methods __init__ ~90 lines processInit ~160 lines processSubmit ~120 lines _processOrder ~220 lines _processVirtualOrder ~110 lines _createUCSOrder ~240 lines _sendWelcomeEmail ~120 lines
    106. 106. class Session(object): ... ~1000 lines 79 methods, mostly small __init__ ~180 lines _buildReturnURL ~125 lines
    107. 107. def update_everything(...): ...
    108. 108. def update_everything(...): ...def do_everything(...): ...
    109. 109. def update_everything(...): ...def do_everything(...): ...def go(...): ...
    110. 110. Refactor
    111. 111. The Seductive Lure of Global State
    112. 112. # gvars.pydctEnv = NoneobjSession = NoneobjWebvars = NoneobjHeaders = NoneobjUserAgent = None
    113. 113. from col.web import gvars...if gvars.objSession.hasSomething(): ...if gvars.objWebvars.get(foo) == bar: ...strUserName = gvars.objSession.objCustomer.getName()
    114. 114. • Highly convenient when writing front-end website code
    115. 115. • Highly convenient when writing front-end website code• Highly painful when doing anything else (scripts, cron jobs, back-end systems)
    116. 116. • Highly convenient when writing front-end website code• Highly painful when doing anything else (scripts, cron jobs, back-end systems)• Any code that uses it is not reusable outside the website context without extensive faking
    117. 117. • Highly convenient when writing front-end website code• Highly painful when doing anything else (scripts, cron jobs, back-end systems)• Any code that uses it is not reusable outside the website context without extensive faking• Safety issues/data leakage
    118. 118. Breaking the Law (of Demeter)
    119. 119. gvars.objSession.objCustomer.objMemberStatus .isPAID()
    120. 120. gvars.objSession.objCustomer.objMemberStatus .isPAID()if gvars.dctEnv[session].getCustomer() .isSignedIn():
    121. 121. gvars.objSession.objCustomer.objMemberStatus .isPAID()if gvars.dctEnv[session].getCustomer() .isSignedIn():current_url = self.objSession._getCurrentURL()
    122. 122. gvars.objSession.objCustomer.objMemberStatus .isPAID()if gvars.dctEnv[session].getCustomer() .isSignedIn():current_url = self.objSession._getCurrentURL()return (iEvent._objGuestList.getGuestList()[0] .getEventSequence())
    123. 123. class MyController(BaseController): @require_auth(...) def my_action(self, request): ...class require_auth(object): ... def __call__(self, function): def decorated(controller, request, *args, **kwargs): user = controller.user if user.signed_in: ... ... return decorated
    124. 124. class MyController(BaseController): @require_auth(...) def my_action(self, request): ...class require_auth(object): ... def __call__(self, function): def decorated(controller, request, *args, **kwargs): user = controller.user if user.signed_in: ... ... return decorated
    125. 125. The Diaper Pattern
    126. 126. def get_important_object(): try: data = talk_to_database(...) return ImportantObject(data) except: passfoo = get_important_object(...)foo.do_something_important()
    127. 127. def get_important_object(): try: data = talk_to_database(...) return ImportantObject(data) except: passfoo = get_important_object(...)foo.do_something_important()
    128. 128. def get_important_object(): try: data = talk_to_database(...) return ImportantObject(data) except: passfoo = get_important_object(...)foo.do_something_important()
    129. 129. def get_important_object(): data = talk_to_database(...) return ImportantObject(data)foo = get_important_object(...)foo.do_something_important()
    130. 130. Creative Ways to Break the Build
    131. 131. class DuckPuncher(object): def __init__(...): ... def setup(...): ... def teardown(...): ... def punch(...): ... def hug(...): ... def with_setup(self, func): def test_func_wrapper(*args, **kwargs): self.setup() ret = func(*args, **kwargs) self.teardown() return ret test_func_wrapper = wraps(func)(test_func_wrapper) return test_func_wrapper
    132. 132. class DuckPuncher(object): def __init__(...): ... def setup(...): ... def teardown(...): ... def punch(...): ... def hug(...): ... def with_setup(self, func): def test_func_wrapper(*args, **kwargs): self.setup() ret = func(*args, **kwargs) self.teardown() return ret test_func_wrapper = wraps(func)(test_func_wrapper) return test_func_wrapper
    133. 133. class DuckPuncher(object): def __init__(...): ... def setup(...): ... def teardown(...): ... def punch(...): ... def hug(...): ... def with_setup(self, func): def test_func_wrapper(*args, **kwargs): self.setup() try: ret = func(*args, **kwargs) finally: self.teardown() return ret test_func_wrapper = wraps(func)(test_func_wrapper) return test_func_wrapper
    134. 134. class DuckPuncher(object): def __init__(...): ... def setup(...): ... def teardown(...): ... def punch(...): ... def hug(...): ... def with_setup(self, func): def test_func_wrapper(*args, **kwargs): self.setup() try: ret = func(*args, **kwargs) finally: self.teardown() return ret test_func_wrapper = wraps(func)(test_func_wrapper) return test_func_wrapper
    135. 135. from mock import patchclass TestMyThing(...): @patch(‘__builtin__.open’): def test_writes_files(self, mock_open): ...
    136. 136. Adventures in Wheel Reinvention
    137. 137. • log
    138. 138. • log• xlog
    139. 139. • log• xlog• simplelog
    140. 140. • log• xlog• simplelog• superlog
    141. 141. • log• xlog• simplelog• superlog• pylogger
    142. 142. • log• xlog• simplelog• superlog• pylogger• prettylog
    143. 143. • log • logging (not stdlib!)• xlog• simplelog• superlog• pylogger• prettylog
    144. 144. • log • logging (not stdlib!)• xlog • logmanager• simplelog• superlog• pylogger• prettylog
    145. 145. • log • logging (not stdlib!)• xlog • logmanager• simplelog • logging (stdlib!)• superlog• pylogger• prettylog
    146. 146. • log • logging (not stdlib!)• xlog • logmanager• simplelog • logging (stdlib!)• superlog • logwrangler• pylogger• prettylog
    147. 147. • log • logging (not stdlib!)• xlog • logmanager• simplelog • logging (stdlib!)• superlog • logwrangler• pylogger • agi.log• prettylog
    148. 148. tl;dr• Hungarian notation BAD• PEP-8 GOOD• Unclean code BAD• Use existing tools GOOD• Weird build breaking BAD
    149. 149. Things to Explore
    150. 150. Things to Explore• Clean Code: http://ASIN.cc/~11GQo
    151. 151. Things to Explore• Clean Code: http://ASIN.cc/~11GQo• http://code.google.com/p/snake-guice/
    152. 152. Things to Explore• Clean Code: http://ASIN.cc/~11GQo• http://code.google.com/p/snake-guice/• http://www.voidspace.org.uk/python/mock/
    153. 153. Links• Twitter: @mpirnat• Blog: http://mike.pirnat.com• Inspiration: • Gary Numan: http://youtu.be/Oj2PR9CI9xY • Joy Division: http://youtu.be/5AqeqAQ1ILI • Hyperbole and a Half: http://hyperboleandahalf.blogspot.com/2010/06/ this-is-why-ill-never-be-adult.html

    ×