How To Write
Reusable Code

Greg Ward <greg@gerg.ca>
@gergdotca
PyCon 2015
Montreal, QC • Apr 11, 2015

Who remembers the 90s?

Oops, sorry, wrong 90s

Honestly, you didn't miss much

mostly clueless pontificators claiming that object-oriented programming would magically usher in an era of reusable component software

...15 years pass...

Abstract
Singleton
Proxy
Factory
Bean

"Convenient proxy factory bean superclass for proxy factory beans that create only singletons." *

* don't ask: it's a Java thing

OOP was not a silver bullet

How to write reusable code?

If I can do it, you can do it

Caveat: is reusability important?

Principles of Reusable Code

  1. be a better programmer
  2. fewer classes, more functions
  3. functions ≠ procedures (aka, beware side effects)
  4. fewer frameworks, more libraries
  5. don't go overboard
  6. don't solve everything
  7. test, test, test
  8. document, document, document
  9. extensibility ≠ reusability

1. Be a better programmer

2. Fewer classes, more functions

Anti-pattern #1

Code smells:

class ThingLoader:
    def __init__(self, host, id):
        self.host = host
        self.id = id

    def load(self):
        # connect to self.host
        # load thing identified by self.id

A better way

def load_thing(host, id):
    # connect to host
    # load thing identified by id

Tip o' the hat to Jack Diederich:

Anti-pattern #2

def is_draft(conn, article_id):
    '''return true if the specified article is unpublished'''

def is_published(conn, article_id):
    '''return true if the specified article is published'''

def load_article(conn, article_id):
    '''load article from database over conn, returning body'''

def save_article(conn, article_id, body):
    '''write article to database over conn'''
  

A better way

class ArticleStore(object)
    def __init__(self, conn):
        self.conn = conn

    def is_draft(self, article_id):
        '''return true if the specified article is unpublished'''

    def is_published(self, article_id):
        '''return true if the specified article is published'''

    def load_article(self, article_id):
        '''load article from database, returning body'''

    def save_article(self, article_id, body):
        '''write article to database'''
  

3. Functions ≠ procedures

Anti-pattern #3

/**
 * Replace all 'e' characters in str with 'E'. Return the number of
 * characters replaced.
 */
int strmunge(string str) {
    ...
}

It's not just C programmers

def get_foo(self, foo=None):
    '''Query the server for foo values.
    Return a dict mapping hostname to foo value.

    foo must be a dict or None; if supplied, the
    foo values will additionally be stored there
    by hostname, and foo will be returned instead
    of a new dict.
    '''
(spotted in a real-life code review, Apr 2015)

A better way

/**
 * Return newstr, a copy of str with all 'e' characters replaced
 * by 'E', and nreplaced, the number of characters replaced.
 */
(string, int) strmunge (string str) {
    ...
}
  

(assume a language with multiple return values)

def get_foo(self):
    '''Query the server for foo values.
    Return a dict mapping hostname to foo value.
    '''
  

4. Fewer frameworks, more libraries

5. Don't go overboard

6. Don't solve everything

7. Test, test, test

8. Document, document, document

9. Extensibility ≠ reusability

Conclusion

References & links

/

#