SlideShare a Scribd company logo
1 of 104
Download to read offline
Fluent,
             Fluid APIs         by Erik Rose

              “A poet is, before anything else, a person
              who is passionately in love with language.”
                           —W. H. Auden




Welcome to Fluid, Fluent APIs or, perhaps more fluently, poetic APIs.

My name is Erik Rose and I work at Mozilla as not a poet.

Nevertheless, I’m in love with language as well—formal languages,
human language, even lexically structured music and dance.

Mark Twain: language is the best way of getting ideas from one head
into another without surgery.

So I want to tell you a story about a man who didn’t have this ability for
27 years.




Nevertheless, all programmers share a love for language as well.

Let me tell you a story about a man who was missing this power—and
even the more basic power of human words.
Poetic APIs            by Erik Rose

              “A poet is, before anything else, a person
              who is passionately in love with language.”
                           —W. H. Auden




Welcome to Fluid, Fluent APIs or, perhaps more fluently, poetic APIs.

My name is Erik Rose and I work at Mozilla as not a poet.

Nevertheless, I’m in love with language as well—formal languages,
human language, even lexically structured music and dance.

Mark Twain: language is the best way of getting ideas from one head
into another without surgery.

So I want to tell you a story about a man who didn’t have this ability for
27 years.




Nevertheless, all programmers share a love for language as well.

Let me tell you a story about a man who was missing this power—and
even the more basic power of human words.
Poetic APIs
                programmer
                              by Erik Rose
                  ^
              “A poet is, before anything else, a person
              who is passionately in love with language.”
                           —W. H. Auden




Welcome to Fluid, Fluent APIs or, perhaps more fluently, poetic APIs.

My name is Erik Rose and I work at Mozilla as not a poet.

Nevertheless, I’m in love with language as well—formal languages,
human language, even lexically structured music and dance.

Mark Twain: language is the best way of getting ideas from one head
into another without surgery.

So I want to tell you a story about a man who didn’t have this ability for
27 years.




Nevertheless, all programmers share a love for language as well.

Let me tell you a story about a man who was missing this power—and
even the more basic power of human words.
Ildefanso
deaf
hearing parents, siblings, stupid?

Susan Schaller
cowering
mimic
imaginery student
pointing
“Oh! Everything has a name!”
“Oh! Everything has a name!”




Now, this was a 27-year-old man who had been around. He didn’t
live in the jungle. But, as Susan describes it, the look on his face
was as if he had never seen a window before. The window became
a different thing by having a symbol attached to it.
“The window became a different thing by
           having a symbol attached to it.”




He had a category for it. He could talk about a window to someone.
Inventing
           Language
        “Any fool can write code that a computer can understand.
       Good programmers write code that humans can understand.”
                            —Martin Fowler



In creating an API, your task is not only to make something that works—
but to put your categories for the universe into somebody else's head—
to choose symbols that all discourse will be in terms of.
As you do so, take care, because: limits the thoughts users will be able to
have about the problem domain.

Ildefanso trouble w/proper names & time

“When did we last meet?” → “In the dry season” or “At the holidays”.
The abstract names and numbers we give to various divisions of time
aren’t the abstractions he’s built his world on.

Before he had lang, he could do simple math with quantites up to about
10, but he couldn’t get much farther until he had words.

Likewise, when you make an API,
opportunity to succeed: magnifying efforts of all those who come after
you
fail: frustrating all those who come after you

Example: urllib2 vs. Requests
req = urllib2.Request(gh_url)
     password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
     password_manager.add_password(
         None, 'https://api.github.com', 'user', 'pass')
     auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
     opener = urllib2.build_opener(auth_manager)
     urllib2.install_opener(opener)
     handler = urllib2.urlopen(req)
     print handler.getcode()




urllib2: stdlib, horrendously verbose, hard to do common things
(partially because it’s not just for HTTP)
Requests: 3rd-party, de facto standard because it harnesses the
common concept of an HTTP get—productivity multiplier

This is why API design makes greatest demands on fluency:

You're drawing out borders around concepts which have to fit a variety
of other people’s brains and be adaptable enough for situations you
haven’t forseen.

I’m going to give you 7 rules of thumb to increase your chances.

The first one is…
req = urllib2.Request(gh_url)
     password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
     password_manager.add_password(
         None, 'https://api.github.com', 'user', 'pass')
     auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
     opener = urllib2.build_opener(auth_manager)
     urllib2.install_opener(opener)
     handler = urllib2.urlopen(req)
     print handler.getcode()




     r = requests.get('https://api.github.com', auth=('user', 'pass'))
     print r.status_code




urllib2: stdlib, horrendously verbose, hard to do common things
(partially because it’s not just for HTTP)
Requests: 3rd-party, de facto standard because it harnesses the
common concept of an HTTP get—productivity multiplier

This is why API design makes greatest demands on fluency:

You're drawing out borders around concepts which have to fit a variety
of other people’s brains and be adaptable enough for situations you
haven’t forseen.

I’m going to give you 7 rules of thumb to increase your chances.

The first one is…
Don’t Be An
        Architecture
         Astronaut
My first step when designing a new library is actually to not design
a new library.

It’s all well and good to seek to do good design in the abstract,
but design is basically imagination,
and imagination is essentially hypothesizing,
and we know from history how well hypotheses do when they’re
not continually tested and brought back to earth by reality.

You end up with alchemy rather than chemistry.
Very impressive to look at and academically interesting but it’s
merely a self-consistent system of nonsense,
not anything of practical use.

So, to bring our efforts back into the realm of science, remember
that the best libraries are extracted, not invented.
The best libraries are extracted, not invented.




What this means is that you should have an application that already
does the things you want your library to do.
[Preferably 2—and as different as possible.]

And then take the bits of them that do the things you’re interested
in and factor them up.
For example, I wrote a plugin nose-progressive for the popular
Python test framework nose,
and its purpose was to display a progress bar and just generally
make output easier to follow.

It made use of a lot of terminal escape codes, for things like
bolding, coloring, and positioning.

The code looked something like this.
For example, I wrote a plugin nose-progressive for the popular
Python test framework nose,
and its purpose was to display a progress bar and just generally
make output easier to follow.

It made use of a lot of terminal escape codes, for things like
bolding, coloring, and positioning.

The code looked something like this.
def terminal_codes(self, stream):
         capabilities = ['bold', 'sc', 'rc', 'sgr0', 'el']
         if hasattr(stream, 'fileno') and isatty(stream.fileno()):
             # Explicit args make setupterm() work even when -s is passed:
             setupterm(None, stream.fileno()) # so things like tigetstr() work
             codes = dict((x, tigetstr(x)) for x in capabilities)
             cup = tigetstr('cup')
             codes['cup'] = lambda line, column: tparm(cup, line, column)
         else:
             # If you're crazy enough to pipe this to a file or something,
             # don't output terminal codes:
             codes = defaultdict(lambda: '', cup=lambda line, column: '')
         return codes

     ...
     self._codes = terminal_codes(stdout)
     ...

     class AtLine(object):
         def __enter__(self):
             """Save position and move to progress bar, col 1."""
             self.stream.write(self._codes['sc']) # save position
             self.stream.write(self._codes['cup'](self.line, 0))

         def __exit__(self, type, value, tb):
             self.stream.write(self._codes['rc']) # restore position

     ...
     print self._codes['bold'] + results + self._codes['sgr0']




Lots of terminal setup and bookkeeping all intertwined with code
that actually kept track of tests.
Raw terminal capability names like sgr0 and rc that I had to keep
looking up. [TODO: red underlines]

Every time I’d have to do some formatting I’d think “Geez, I wish
there were a decent abstraction around this so I wouldn’t have to
look at it.” It’s at this point—when you have a real, useful program
with a library struggling to get out—that you can come down from
orbit and start thinking about library design.

So it was time for blessings—my terminal formatting library—to be
born.
Tasks




The first thing to do is to dump everything out on the workbench.
What sort of things ought the library to do? bam bam

Now, what tools have we got at our workbench? Well…all the stuff in
our language…
Tasks
                   Print some text with formatting.




The first thing to do is to dump everything out on the workbench.
What sort of things ought the library to do? bam bam

Now, what tools have we got at our workbench? Well…all the stuff in
our language…
Tasks
                   Print some text with formatting.
             Print some text at a location, then snap back.




The first thing to do is to dump everything out on the workbench.
What sort of things ought the library to do? bam bam

Now, what tools have we got at our workbench? Well…all the stuff in
our language…
Language Constructs
              Functions, arguments, keyword arguments
                              Decorators
                          Context managers
           Classes (really more of an implementation detail)




bam bam bam bam

common patterns…:

bam bam bam

Then we shake the workbench up and see if we can find some good
pairings between tools and tasks. What guides you? Consistency.
Language Constructs
              Functions, arguments, keyword arguments
                              Decorators
                          Context managers
           Classes (really more of an implementation detail)


                               å
    Patterns, Protocols, Interfaces, and Conventions
                             Sequences
                              Iterators
                             Mappings


bam bam bam bam

common patterns…:

bam bam bam

Then we shake the workbench up and see if we can find some good
pairings between tools and tasks. What guides you? Consistency.
Consistency
                 “Think like a wise man, but communicate
                      in the language of the people.”
                          —William Butler Yeats



You have the entire technical and cultural weight of the language itself
behind you as well as accumulated habits of influential libs and the
programmer community.

When we're designing web sites, we think of it this way: people spend 90%
of their time on other people's web sites, so…
make the logo link to the home page
put the log-in link in the upper right
and so on

When writing an API, realize:
users will spend 90% time using other APIs
You can be weird and clever if you need to be, but if you can manage to
stick with conventions, you’ll get these bonuses…

1. conveys respect for your users. You're not some outsider who is going
to make their code a mishmash of clashing styles.
I can't tell you how many times I've encountered a Java API masquerading
as Python one and thrown it away in disgust.
getters and setters + violation of case conventions → I'm going to move on.
Brilliant thing about early Mac project: explicit HIGs
Every program same commands: open, close, quit, copy, paste. In same
menus w/same keyboard shortcuts.
Learn one program → learned 'em all

If you implement get(key, default) and call it that, people will pick it up
faster and remember it better than fetch(default, key).

3. Polymorphism. Can use calling code on dicts or whatever your class is.
get(key, default)          >    fetch(default, key)




Brilliant thing about early Mac project: explicit HIGs
Every program same commands: open, close, quit, copy, paste. In same
menus w/same keyboard shortcuts.
Learn one program → learned 'em all

If you implement get(key, default) and call it that, people will pick it up
faster and remember it better than fetch(default, key).

3. Polymorphism. Can use calling code on dicts or whatever your class is.
Tasks
              Print some text at a location, then snap back.
                    Print some text with formatting.




So, coming back to our tasks at hand, how do we bang those tasks
against existing language features and conventions?

Well, I like to get as many alternatives out on the workbench as
possible. I call these sketches.
Really only 2 choices for printing at a location.

Both are okay, but the 2nd gives us the flexibility to put multiple
statements inside—even calls out to other functions.

And then the first can be written in terms of it, if you like.
Tasks
              Print some text at a location, then snap back.
                    Print some text with formatting.



     print_at('Hello world', 10, 2)

     with location(10, 2):
         print 'Hello world'
         for thing in range(10):
             print thing




So, coming back to our tasks at hand, how do we bang those tasks
against existing language features and conventions?

Well, I like to get as many alternatives out on the workbench as
possible. I call these sketches.
Really only 2 choices for printing at a location.

Both are okay, but the 2nd gives us the flexibility to put multiple
statements inside—even calls out to other functions.

And then the first can be written in terms of it, if you like.
Tasks
              Print some text at a location, then snap back.
                    Print some text with formatting.



     print_formatted('Hello world', 'red', 'bold')
     print color('Hello world', 'red', 'bold')
     print color('<red><bold>Hello world</bold></red>')
     print red(bold('Hello') + 'world')

     print codes['red'] + codes['bold'] + 
           'Hello world' + codes['normal']

     print '{t.red}Hi{t.bold}Mom{t.normal}'.format(t=terminal)
     print terminal.red_bold + 'Hello world' + terminal.normal




Here are sketches for text formatting. We see some square
brackets here, some dots, some string concatenation, some
function calls with kwargs and with positional args. Most separate
the printing from the formating, but one combines them.

I have a couple of favorites here.

this…common function call syntax…composable. It frankly looks
like HTML.

trouble: no portable definition of a "turn [bold, for example] off"
escape code, so state

have to embed it in the string or in a parallel data structure

-> enormous code complexity & mental load for users

went with attrs
...we save a great deal of complexity—both internally and for the
caller
Consistent: use with Python’s built-in templating language: less to
Tasks
              Print some text at a location, then snap back.
                    Print some text with formatting.



     print_formatted('Hello world', 'red', 'bold')
     print color('Hello world', 'red', 'bold')
     print color('<red><bold>Hello world</bold></red>')
     print red(bold('Hello') + 'world')

     print codes['red'] + codes['bold'] + 
           'Hello world' + codes['normal']

     print '{t.red}Hi{t.bold}Mom{t.normal}'.format(t=terminal)
     print terminal.red_bold + 'Hello world' + terminal.normal




Here are sketches for text formatting. We see some square
brackets here, some dots, some string concatenation, some
function calls with kwargs and with positional args. Most separate
the printing from the formating, but one combines them.

I have a couple of favorites here.

this…common function call syntax…composable. It frankly looks
like HTML.

trouble: no portable definition of a "turn [bold, for example] off"
escape code, so state

have to embed it in the string or in a parallel data structure

-> enormous code complexity & mental load for users

went with attrs
...we save a great deal of complexity—both internally and for the
caller
Consistent: use with Python’s built-in templating language: less to
Tasks
              Print some text at a location, then snap back.
                    Print some text with formatting.



     print_formatted('Hello world', 'red', 'bold')
     print color('Hello world', 'red', 'bold')
     print color('<red><bold>Hello world</bold></red>')
     print red(bold('Hello') + 'world')

     print codes['red'] + codes['bold'] + 
           'Hello world' + codes['normal']

     print '{t.red}Hi{t.bold}Mom{t.normal}'.format(t=terminal)
     print terminal.red_bold + 'Hello world' + terminal.normal




Here are sketches for text formatting. We see some square
brackets here, some dots, some string concatenation, some
function calls with kwargs and with positional args. Most separate
the printing from the formating, but one combines them.

I have a couple of favorites here.

this…common function call syntax…composable. It frankly looks
like HTML.

trouble: no portable definition of a "turn [bold, for example] off"
escape code, so state

have to embed it in the string or in a parallel data structure

-> enormous code complexity & mental load for users

went with attrs
...we save a great deal of complexity—both internally and for the
caller
Consistent: use with Python’s built-in templating language: less to
from blessings import Terminal

      t = Terminal()

      print t.red_bold + 'Hello world' + t.normal
      print t.red_on_white + 'Hello world' + t.normal
      print t.underline_red_on_green + 'Hello world' + t.normal




But, if you’re going to do something a little weird, at least be self-
consistent. This mashed-together syntax is used for everything in
Blessings: formatting, foreground color, background color. It’s hard to get
wrong; you can put the formatters in any order, and it just works.

Thus, the user doesn’t have to keep making trips back to the docs. They
internalize one slightly odd idea, and that carries them through all their use
cases.
Consistency            warning signs




So when you’re evaluating your consistency, look out for these red flags:
Frequent refs to your own docs or source code as you’re building it.
(“Hmm, what did I call that?” “What order does it go in?”)
! Example: "index" kwarg vs. "indexes" in pyelasticsearch
Feeling like you’re inventing novel syntax. Use judgment: designs paying
off rather than showing off.
Consistency             warning signs
                Frequent references to your docs or source




So when you’re evaluating your consistency, look out for these red flags:
Frequent refs to your own docs or source code as you’re building it.
(“Hmm, what did I call that?” “What order does it go in?”)
! Example: "index" kwarg vs. "indexes" in pyelasticsearch
Feeling like you’re inventing novel syntax. Use judgment: designs paying
off rather than showing off.
Consistency             warning signs
                Frequent references to your docs or source
                       Feeling syntactically clever




So when you’re evaluating your consistency, look out for these red flags:
Frequent refs to your own docs or source code as you’re building it.
(“Hmm, what did I call that?” “What order does it go in?”)
! Example: "index" kwarg vs. "indexes" in pyelasticsearch
Feeling like you’re inventing novel syntax. Use judgment: designs paying
off rather than showing off.
Brevity
                 “The finest language is mostly made up
                     of simple, unimposing words.”
                              —George Eliot



3rd rule of thumb is Brevity.

Make common things…
short (in tokens, sometimes even in terms of chars if it doesn’t hurt
understanding)
from blessings import Terminal
      term = Terminal()

      print term.bold + 'I am bold!' + term.normal




excerpt from earlier
now you’ll see why (mashed-together)

Since formatting something and then resetting to plain text is the most
common operation, we make it the shortest.

Not everything has to be brief.

Another way to think about it: pay for what you eat.
from blessings import Terminal
      term = Terminal()

      print term.bold + 'I am bold!' + term.normal

      print term.bold('I am bold!')




excerpt from earlier
now you’ll see why (mashed-together)

Since formatting something and then resetting to plain text is the most
common operation, we make it the shortest.

Not everything has to be brief.

Another way to think about it: pay for what you eat.
from blessings import Terminal
      term = Terminal()

      print term.bold + 'I am bold!' + term.normal

      print term.bold('I am bold!')

      print '{t.bold}Very {t.red}emphasized{t.normal}'.format(t=term)




excerpt from earlier
now you’ll see why (mashed-together)

Since formatting something and then resetting to plain text is the most
common operation, we make it the shortest.

Not everything has to be brief.

Another way to think about it: pay for what you eat.
Brevity
 warning signs
Brevity  warning signs
Copying and pasting when writing against your API
Brevity  warning signs
Copying and pasting when writing against your API
  Typing something irrelevant while grumbling
  “Why can’t it just assume the obvious thing?”
Composability
       “Perfection is achieved not when there is nothing left to add
               but when there is nothing left to take away.”
                       —Antoine de Saint-Exupery



Making your abstractions composable means being able to reuse
them in many different situations and plug them together in
different ways.

Million ways to say this: flexibility, loose coupling, small pieces
loosely joined.
It all comes down to the minimization of assumptions.

There are two ways you can do this, one of them wrong.
Let’s go back to another way we could have done Blessings.

Jeff Quast put together a really neat BBS called x84. It works over
telnet using Blessings. Multiprocess design. Most of the action,
including formatting, happens in parent process, and children
handle connections.

With the above, there’s no way to pass the output to the children,
because we assumed (there’s that coupling) we’d be formatting and
printing all in one go.

The wrong way to solve this is to add an option. Print to some file
handle. But then you have to set up a file handle, even if you just
want to keep a string in memory. Plus it makes the API more
complicated: you have to document the option, and there’s a branch
in the code for it. Then you have to test it.
Every time I think about adding an option, I look around for any
other escape route first.

Better way: break that coupling.
print_formatted('Hello world', 'red', 'bold')




Let’s go back to another way we could have done Blessings.

Jeff Quast put together a really neat BBS called x84. It works over
telnet using Blessings. Multiprocess design. Most of the action,
including formatting, happens in parent process, and children
handle connections.

With the above, there’s no way to pass the output to the children,
because we assumed (there’s that coupling) we’d be formatting and
printing all in one go.

The wrong way to solve this is to add an option. Print to some file
handle. But then you have to set up a file handle, even if you just
want to keep a string in memory. Plus it makes the API more
complicated: you have to document the option, and there’s a branch
in the code for it. Then you have to test it.
Every time I think about adding an option, I look around for any
other escape route first.

Better way: break that coupling.
print_formatted('Hello world', 'red', 'bold')

     print_formatted('Hello world', 'red', 'bold', out=some_file)




Let’s go back to another way we could have done Blessings.

Jeff Quast put together a really neat BBS called x84. It works over
telnet using Blessings. Multiprocess design. Most of the action,
including formatting, happens in parent process, and children
handle connections.

With the above, there’s no way to pass the output to the children,
because we assumed (there’s that coupling) we’d be formatting and
printing all in one go.

The wrong way to solve this is to add an option. Print to some file
handle. But then you have to set up a file handle, even if you just
want to keep a string in memory. Plus it makes the API more
complicated: you have to document the option, and there’s a branch
in the code for it. Then you have to test it.
Every time I think about adding an option, I look around for any
other escape route first.

Better way: break that coupling.
print_formatted('Hello world', 'red', 'bold')

     print_formatted('Hello world', 'red', 'bold', out=some_file)

     print formatted('Hello world', 'red', 'bold')




Let’s go back to another way we could have done Blessings.

Jeff Quast put together a really neat BBS called x84. It works over
telnet using Blessings. Multiprocess design. Most of the action,
including formatting, happens in parent process, and children
handle connections.

With the above, there’s no way to pass the output to the children,
because we assumed (there’s that coupling) we’d be formatting and
printing all in one go.

The wrong way to solve this is to add an option. Print to some file
handle. But then you have to set up a file handle, even if you just
want to keep a string in memory. Plus it makes the API more
complicated: you have to document the option, and there’s a branch
in the code for it. Then you have to test it.
Every time I think about adding an option, I look around for any
other escape route first.

Better way: break that coupling.
Composabilty
                             warning signs




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                          Classes with lots of state




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                                          warning signs
                                       Classes with lots of state
  class ElasticSearch(object):
      """
      An object which manages connections to elasticsearch and acts as a
      go-between for API calls to it"""

     def index(self, index, doc_type, doc, id=None, force_insert=False,
               query_params=None):
         """Put a typed JSON document into a specific index to make it
         searchable."""

     def search(self, query, **kwargs):
         """Execute a search query against one or more indices and get back search
         hits."""

     def more_like_this(self, index, doc_type, id, mlt_fields, body='', query_params=None):
         """Execute a "more like this" search query against one or more fields and
         get back search hits."""

      . . .




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                                           warning signs
                                       Classes with lots of state
  class ElasticSearch(object):
      """
                                 class PenaltyBox(object):
      An object which manages connections to elasticsearch and acts as a
                                     """A thread-safe bucket of servers (or other things) that may have
      go-between for API calls to it"""
                                     downtime."""
      def index(self, index, doc_type, doc, id=None, force_insert=False,
                query_params=None): def get(self):
          """Put a typed JSON document into a specific index server and a bool indicating whether it was from the
                                         """Return a random to make it
          searchable."""                 dead list."""

      def search(self, query, **kwargs): mark_dead(self, server):
                                     def
          """Execute a search query against one or more indices and get won't search
                                         """Guarantee that this server back be returned again until a period of
          hits."""                       time has passed, unless all servers are dead."""

      def more_like_this(self, index, def mark_live(self, server):
                                       doc_type, id, mlt_fields, body='', query_params=None):
          """Execute a "more like this" search query against one or dead list to and live one."""
                                          """Move a server from the more fields the
          get back search hits."""

      . . .




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                         Classes with lots of state
                        Deep inheritance hierarchies




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                         Classes with lots of state
                        Deep inheritance hierarchies
                      Violations of the Law of Demeter




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                         Classes with lots of state
                        Deep inheritance hierarchies
                      Violations of the Law of Demeter
                              Mocking in tests




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                         Classes with lots of state
                        Deep inheritance hierarchies
                      Violations of the Law of Demeter
                              Mocking in tests

      print_formatted('Hello world', 'red', 'bold')




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                         Classes with lots of state
                        Deep inheritance hierarchies
                      Violations of the Law of Demeter
                              Mocking in tests

      print_formatted('Hello world', 'red', 'bold')

      print formatted('Hello world', 'red', 'bold')




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Composabilty
                             warning signs
                         Classes with lots of state
                        Deep inheritance hierarchies
                      Violations of the Law of Demeter
                              Mocking in tests
                                   Options
      print_formatted('Hello world', 'red', 'bold')

      print formatted('Hello world', 'red', 'bold')




Watch out for these red flags:

1. Classes with lots of state make me think there are multiple little classes
hiding inside, each serving a single purpose. For example,
pyelasticsearch connection object…needed penalty box for downed
nodes…tempting to add directly…better to separate…now can reuse in
other projects. Doesn’t even know about connections—just strings—so
represent whatever you want.

2. Deep inheritance hierarchies. People say “composition is better than
inheritance”, and here’s why. If you inherit, your object gets easy access to
the functionality of the parent—true. But it also inherits all the invariant
baggage of its direct parent and all the parents up the line. It’s required to
maintain those invariants, but it doesn’t get any help from the language,
which say it’s perfectly welcome to muck with private vars however it likes.
Subclassing just increases the amount of code that has to tiptoe around a
collection of state.

Instead it’s often better to instantiate the object you need inside yours and
stick it in an instance var.
Plain Data
                “All the great things are simple, and many
                  can be expressed in a single word…”
                         —Sir Winston Churchill



Another rule of thumb that helps with reusability is plain data.
Whenever possible, your API should speak with its callers using
simple, built-in types, not big hairy data structures.

The aim is to reduce barriers to reuse. What’s a barrier look like?
It looks like the ConfigParser class out of the Python stdlib. Here’s an
excerpt of its API. Its purpose in life is to read INI files.

Unfortunately, it’s not just a parser that does its jobs and translates
the parsed content into something more usable, like a dictionary.
Instead, the config ends up represented as this unwieldy ConfigParser
object with lots of custom-named methods that don’t conform to any
Python idiom. This means that any code that uses ConfigParser ends
up written in its terms.

And, if you want the flexibility to pull in configuration from some
source that isn’t an INI file, you’ll have to write your own abstraction
layer on top of ConfigParser.

I’m going to continue to pick on ConfigParser (and pretend it has a
slightly worse API for illustrative purposes). (Actually takes multiple
filenames and does so for a fairly good reason.)

But let’s pretend it takes a single config file name. This might feel like
a convenience, since config often comes from files, but it’s really a
barrier.
✚
It looks like the ConfigParser class out of the Python stdlib. Here’s an
excerpt of its API. Its purpose in life is to read INI files.

Unfortunately, it’s not just a parser that does its jobs and translates
the parsed content into something more usable, like a dictionary.
Instead, the config ends up represented as this unwieldy ConfigParser
object with lots of custom-named methods that don’t conform to any
Python idiom. This means that any code that uses ConfigParser ends
up written in its terms.

And, if you want the flexibility to pull in configuration from some
source that isn’t an INI file, you’ll have to write your own abstraction
layer on top of ConfigParser.

I’m going to continue to pick on ConfigParser (and pretend it has a
slightly worse API for illustrative purposes). (Actually takes multiple
filenames and does so for a fairly good reason.)

But let’s pretend it takes a single config file name. This might feel like
a convenience, since config often comes from files, but it’s really a
barrier.
✚
It looks like the ConfigParser class out of the Python stdlib. Here’s an
excerpt of its API. Its purpose in life is to read INI files.

Unfortunately, it’s not just a parser that does its jobs and translates
the parsed content into something more usable, like a dictionary.
Instead, the config ends up represented as this unwieldy ConfigParser
object with lots of custom-named methods that don’t conform to any
Python idiom. This means that any code that uses ConfigParser ends
up written in its terms.

And, if you want the flexibility to pull in configuration from some
source that isn’t an INI file, you’ll have to write your own abstraction
layer on top of ConfigParser.

I’m going to continue to pick on ConfigParser (and pretend it has a
slightly worse API for illustrative purposes). (Actually takes multiple
filenames and does so for a fairly good reason.)

But let’s pretend it takes a single config file name. This might feel like
a convenience, since config often comes from files, but it’s really a
barrier.
✚        ✚

It looks like the ConfigParser class out of the Python stdlib. Here’s an
excerpt of its API. Its purpose in life is to read INI files.

Unfortunately, it’s not just a parser that does its jobs and translates
the parsed content into something more usable, like a dictionary.
Instead, the config ends up represented as this unwieldy ConfigParser
object with lots of custom-named methods that don’t conform to any
Python idiom. This means that any code that uses ConfigParser ends
up written in its terms.

And, if you want the flexibility to pull in configuration from some
source that isn’t an INI file, you’ll have to write your own abstraction
layer on top of ConfigParser.

I’m going to continue to pick on ConfigParser (and pretend it has a
slightly worse API for illustrative purposes). (Actually takes multiple
filenames and does so for a fairly good reason.)

But let’s pretend it takes a single config file name. This might feel like
a convenience, since config often comes from files, but it’s really a
barrier.
✚         ✚
    RawConfigParser.parse(string)




It looks like the ConfigParser class out of the Python stdlib. Here’s an
excerpt of its API. Its purpose in life is to read INI files.

Unfortunately, it’s not just a parser that does its jobs and translates
the parsed content into something more usable, like a dictionary.
Instead, the config ends up represented as this unwieldy ConfigParser
object with lots of custom-named methods that don’t conform to any
Python idiom. This means that any code that uses ConfigParser ends
up written in its terms.

And, if you want the flexibility to pull in configuration from some
source that isn’t an INI file, you’ll have to write your own abstraction
layer on top of ConfigParser.

I’m going to continue to pick on ConfigParser (and pretend it has a
slightly worse API for illustrative purposes). (Actually takes multiple
filenames and does so for a fairly good reason.)

But let’s pretend it takes a single config file name. This might feel like
a convenience, since config often comes from files, but it’s really a
barrier.
✚         ✚
    RawConfigParser.parse(string)

    charming_parser.parse(file_contents('some_file.ini'))




It looks like the ConfigParser class out of the Python stdlib. Here’s an
excerpt of its API. Its purpose in life is to read INI files.

Unfortunately, it’s not just a parser that does its jobs and translates
the parsed content into something more usable, like a dictionary.
Instead, the config ends up represented as this unwieldy ConfigParser
object with lots of custom-named methods that don’t conform to any
Python idiom. This means that any code that uses ConfigParser ends
up written in its terms.

And, if you want the flexibility to pull in configuration from some
source that isn’t an INI file, you’ll have to write your own abstraction
layer on top of ConfigParser.

I’m going to continue to pick on ConfigParser (and pretend it has a
slightly worse API for illustrative purposes). (Actually takes multiple
filenames and does so for a fairly good reason.)

But let’s pretend it takes a single config file name. This might feel like
a convenience, since config often comes from files, but it’s really a
barrier.
class Node(dict):
        """A wrapper around a native Reflect.parse dict
        providing some convenience methods and some caching
        of expensive computations"""




Even when you have an inherently more complicated data structure,
expose as much of it as possible as built-in interfaces.

Spiderflunky…doubly-linked tree…transform to iterator: walk_up
and walk_down.

Sexy list comps




Notice what this has done: it’s forced the caller to implement their
stuff in terms of your interface. Now they’re stuck with your library
forever, unless they want to rewrite their code. Why not meet on
common turf, like this? (Just return a map, or at least something that
acts like one.)

This is about the language that your API speaks with its callers. You
have a couple of choices here:

You can speak their language, or you can force them to speak yours.
class Node(dict):
        """A wrapper around a native Reflect.parse dict
        providing some convenience methods and some caching
        of expensive computations"""

        def walk_up(self):
            """Yield each node from here to the root of the
            tree, starting with myself."""
            node = self
            while node:
                yield node
                node = node.get('_parent')




Even when you have an inherently more complicated data structure,
expose as much of it as possible as built-in interfaces.

Spiderflunky…doubly-linked tree…transform to iterator: walk_up
and walk_down.

Sexy list comps




Notice what this has done: it’s forced the caller to implement their
stuff in terms of your interface. Now they’re stuck with your library
forever, unless they want to rewrite their code. Why not meet on
common turf, like this? (Just return a map, or at least something that
acts like one.)

This is about the language that your API speaks with its callers. You
have a couple of choices here:

You can speak their language, or you can force them to speak yours.
class Node(dict):
        """A wrapper around a native Reflect.parse dict
        providing some convenience methods and some caching
        of expensive computations"""

        def walk_up(self):
            """Yield each node from here to the root of the
            tree, starting with myself."""
            node = self
            while node:
                yield node
                node = node.get('_parent')

        def nearest_scope_holder(self):
            """Return the nearest node that can have its own
            scope, potentially including myself.

            This will be either a FunctionDeclaration or a
            Program (for now).

            """
            return first(n for n in self.walk_up() if
                         n['type'] in ['FunctionDeclaration',
                                       'Program'])



Even when you have an inherently more complicated data structure,
expose as much of it as possible as built-in interfaces.

Spiderflunky…doubly-linked tree…transform to iterator: walk_up
and walk_down.

Sexy list comps




Notice what this has done: it’s forced the caller to implement their
stuff in terms of your interface. Now they’re stuck with your library
forever, unless they want to rewrite their code. Why not meet on
common turf, like this? (Just return a map, or at least something that
acts like one.)

This is about the language that your API speaks with its callers. You
have a couple of choices here:

You can speak their language, or you can force them to speak yours.
Plain Data         warning signs




(exception: inversion of control. Data, not control.)
Plain Data         warning signs

      Users immediately transforming your output to another format




(exception: inversion of control. Data, not control.)
Plain Data         warning signs

      Users immediately transforming your output to another format
             Instantiating one object just to pass it to another




(exception: inversion of control. Data, not control.)
Grooviness
        “The bad teacher’s words fall on his pupils like harsh rain;
                  the good teacher’s, as gently as dew.”
                         —Talmud: Ta’anith 7b



I have this idea that users of my APIs should spend most of their
time “in the groove.” And I don’t just mean performing well or
doing their work easily. I mean it in a more physical sense.

Think of programming as walking across a plane. The groove is a
nice, smooth path where it’s easy to walk. You could even walk it
with your eyes closed, because the sloping sides would guide you
gently back to the center. Note that there’s nothing keeping you
from stepping out of the groove and walking somewhere else if you
like; it’s not a wall you have to vault over—we’ll get to those in a
minute. But the groove is very attractive, and new users are drawn
there first. Grooves are about guidance, not limitations.

Here are some ideas on how to cut grooves in your APIs.
Grooviness
                  Groove                           Wall




I have this idea that users of my APIs should spend most of their
time “in the groove.” And I don’t just mean performing well or
doing their work easily. I mean it in a more physical sense.

Think of programming as walking across a plane. The groove is a
nice, smooth path where it’s easy to walk. You could even walk it
with your eyes closed, because the sloping sides would guide you
gently back to the center. Note that there’s nothing keeping you
from stepping out of the groove and walking somewhere else if you
like; it’s not a wall you have to vault over—we’ll get to those in a
minute. But the groove is very attractive, and new users are drawn
there first. Grooves are about guidance, not limitations.

Here are some ideas on how to cut grooves in your APIs.
Avoid nonsense representations.




Avoid nonsense representations

I maintain a Python API for elasticsearch called pyelasticsearch.

There are 2 ways to query it: JSON and simpler string syntax.
There’s no such thing as a query that uses both formats at once;
you have to choose one.
Avoid nonsense representations.
     {
         "bool" : {
             "must" : {
                 "term" : { "user" : "fred" }
             },
             "must_not" : {
                 "range" : {
                     "age" : { "from" : 12, "to" : 21 }
                 }
             },
             "minimum_number_should_match" : 1,
             "boost" : 1.0
         }
     }




Avoid nonsense representations

I maintain a Python API for elasticsearch called pyelasticsearch.

There are 2 ways to query it: JSON and simpler string syntax.
There’s no such thing as a query that uses both formats at once;
you have to choose one.
Avoid nonsense representations.
     {
         "bool" : {
             "must" : {
                 "term" : { "user" : "fred" }
             },
             "must_not" : {
                 "range" : {
                     "age" : { "from" : 12, "to" : 21 }
                 }
             },
             "minimum_number_should_match" : 1,
             "boost" : 1.0
         }
     }

                                            frob*ator AND snork




Avoid nonsense representations

I maintain a Python API for elasticsearch called pyelasticsearch.

There are 2 ways to query it: JSON and simpler string syntax.
There’s no such thing as a query that uses both formats at once;
you have to choose one.
Old pyelasticsearch
  def search(self, q=None, body=None, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""




Here’s what pyelasticsearch used to look like.

All its args were optional. You’d pass string-style queries in using
the q argument and JSON-style queries using the body one.

Of course, this meant nothing stopped you from doing this or even
this. But those are nonsense. You shouldn’t even be able to get that
far, not to mention the code that has to exist in your library to check
for and reject such silliness.

New pyelasticsearch replaces that mess with a single required
argument to which you can pass either a string or a JSON dictionary.
You can’t go wrong, because the interpreter won’t let you. Much
groovier!

Incidentally, this turns up another principle: fail shallowly. In
languages that give you tracebacks, this helps. If, heaven forbid,
you’re using a language that doesn’t, this is vital.
Old pyelasticsearch
  def search(self, q=None, body=None, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""

  search(q='frob*ator AND snork', body={'some': 'query'})




Here’s what pyelasticsearch used to look like.

All its args were optional. You’d pass string-style queries in using
the q argument and JSON-style queries using the body one.

Of course, this meant nothing stopped you from doing this or even
this. But those are nonsense. You shouldn’t even be able to get that
far, not to mention the code that has to exist in your library to check
for and reject such silliness.

New pyelasticsearch replaces that mess with a single required
argument to which you can pass either a string or a JSON dictionary.
You can’t go wrong, because the interpreter won’t let you. Much
groovier!

Incidentally, this turns up another principle: fail shallowly. In
languages that give you tracebacks, this helps. If, heaven forbid,
you’re using a language that doesn’t, this is vital.
Old pyelasticsearch
  def search(self, q=None, body=None, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""

  search(q='frob*ator AND snork', body={'some': 'query'})
  search()




Here’s what pyelasticsearch used to look like.

All its args were optional. You’d pass string-style queries in using
the q argument and JSON-style queries using the body one.

Of course, this meant nothing stopped you from doing this or even
this. But those are nonsense. You shouldn’t even be able to get that
far, not to mention the code that has to exist in your library to check
for and reject such silliness.

New pyelasticsearch replaces that mess with a single required
argument to which you can pass either a string or a JSON dictionary.
You can’t go wrong, because the interpreter won’t let you. Much
groovier!

Incidentally, this turns up another principle: fail shallowly. In
languages that give you tracebacks, this helps. If, heaven forbid,
you’re using a language that doesn’t, this is vital.
Old pyelasticsearch
  def search(self, q=None, body=None, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""

  search(q='frob*ator AND snork', body={'some': 'query'})
  search()
✚


Here’s what pyelasticsearch used to look like.

All its args were optional. You’d pass string-style queries in using
the q argument and JSON-style queries using the body one.

Of course, this meant nothing stopped you from doing this or even
this. But those are nonsense. You shouldn’t even be able to get that
far, not to mention the code that has to exist in your library to check
for and reject such silliness.

New pyelasticsearch replaces that mess with a single required
argument to which you can pass either a string or a JSON dictionary.
You can’t go wrong, because the interpreter won’t let you. Much
groovier!

Incidentally, this turns up another principle: fail shallowly. In
languages that give you tracebacks, this helps. If, heaven forbid,
you’re using a language that doesn’t, this is vital.
Old pyelasticsearch
  def search(self, q=None, body=None, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""

  search(q='frob*ator AND snork', body={'some': 'query'})
  search()
✚
                      New pyelasticsearch
  def search(self, query, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""




Here’s what pyelasticsearch used to look like.

All its args were optional. You’d pass string-style queries in using
the q argument and JSON-style queries using the body one.

Of course, this meant nothing stopped you from doing this or even
this. But those are nonsense. You shouldn’t even be able to get that
far, not to mention the code that has to exist in your library to check
for and reject such silliness.

New pyelasticsearch replaces that mess with a single required
argument to which you can pass either a string or a JSON dictionary.
You can’t go wrong, because the interpreter won’t let you. Much
groovier!

Incidentally, this turns up another principle: fail shallowly. In
languages that give you tracebacks, this helps. If, heaven forbid,
you’re using a language that doesn’t, this is vital.
Old pyelasticsearch
  def search(self, q=None, body=None, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""

  search(q='frob*ator AND snork', body={'some': 'query'})
  search()
✚
                      New pyelasticsearch
  def search(self, query, indexes=None, doc_types=None):
      """Execute an elasticsearch query, and return the results."""




                          Fail shallowly.

Here’s what pyelasticsearch used to look like.

All its args were optional. You’d pass string-style queries in using
the q argument and JSON-style queries using the body one.

Of course, this meant nothing stopped you from doing this or even
this. But those are nonsense. You shouldn’t even be able to get that
far, not to mention the code that has to exist in your library to check
for and reject such silliness.

New pyelasticsearch replaces that mess with a single required
argument to which you can pass either a string or a JSON dictionary.
You can’t go wrong, because the interpreter won’t let you. Much
groovier!

Incidentally, this turns up another principle: fail shallowly. In
languages that give you tracebacks, this helps. If, heaven forbid,
you’re using a language that doesn’t, this is vital.
Resource acquisition is initialization.




Another way to provide grooves is RAII.

Here’s a poppable balloon class. But, as you can see, when you first
construct one, it isn’t poppable. You can pop a balloon that has no
air in it. You might imagine the documentation says “Before popping
the balloon, fill it with the desired amount of air.” That’s broken
design. A poppable balloon should be poppable, full-stop. The user
shouldn’t have to head into the docs to figure out what’s wrong.

A better way is to take the initial fill amount at construction. The
user is required to provide it and gets a prompt error if he doesn’t,
so there’s no wondering later why the balloon didn’t make any noise
when you prick it.

RAII is a more specific case of “Don’t have invariants that aren’t.”
Resource acquisition is initialization.
   class PoppableBalloon(object):
       """A balloon you can pop"""

       def __init__(self):
           self.air = 0

       def fill(self, how_much):
           self.air = how_much




Another way to provide grooves is RAII.

Here’s a poppable balloon class. But, as you can see, when you first
construct one, it isn’t poppable. You can pop a balloon that has no
air in it. You might imagine the documentation says “Before popping
the balloon, fill it with the desired amount of air.” That’s broken
design. A poppable balloon should be poppable, full-stop. The user
shouldn’t have to head into the docs to figure out what’s wrong.

A better way is to take the initial fill amount at construction. The
user is required to provide it and gets a prompt error if he doesn’t,
so there’s no wondering later why the balloon didn’t make any noise
when you prick it.

RAII is a more specific case of “Don’t have invariants that aren’t.”
Resource acquisition is initialization.
   class PoppableBalloon(object):
       """A balloon you can pop"""

       def __init__(self):
           self.air = 0

       def fill(self, how_much):
           self.air = how_much




   class PoppableBalloon(object):
       """A balloon you can pop"""

       def __init__(self, initial_fill):
           self.air = initial_fill

       def fill(self, how_much):
           self.air = how_much


Another way to provide grooves is RAII.

Here’s a poppable balloon class. But, as you can see, when you first
construct one, it isn’t poppable. You can pop a balloon that has no
air in it. You might imagine the documentation says “Before popping
the balloon, fill it with the desired amount of air.” That’s broken
design. A poppable balloon should be poppable, full-stop. The user
shouldn’t have to head into the docs to figure out what’s wrong.

A better way is to take the initial fill amount at construction. The
user is required to provide it and gets a prompt error if he doesn’t,
so there’s no wondering later why the balloon didn’t make any noise
when you prick it.

RAII is a more specific case of “Don’t have invariants that aren’t.”
Compelling examples




A final way of providing grooves is through compelling examples.
These are the most overt sort of grooves. The user knows he’s being
advised.

This screenshot from MacPaint 1.0, with its palettes on the left and
menus on the top should look familiar to everybody in this room—
even if they never used it—because Photoshop and Illustrator and
the GIMP and Pixelmator and Paintshop Pro have been following its
example for the past 30 years.

[Another example: Nintendo platformers. Picture is in the margin.]

Set a good example, and people will follow it forever. I used to write
example code in a very pedagogical fashion, with outrageously long
variable names and inefficiency everywhere. I ended up having to
stop that, because users of my libs were copying and pasting my
code for use in production! Oftentimes, they would even leave the
comments!

So don't underestimate the docility of your users; they will do what
you suggest. This isn't because they're stupid. It's just that they're
Compelling examples




A final way of providing grooves is through compelling examples.
These are the most overt sort of grooves. The user knows he’s being
advised.

This screenshot from MacPaint 1.0, with its palettes on the left and
menus on the top should look familiar to everybody in this room—
even if they never used it—because Photoshop and Illustrator and
the GIMP and Pixelmator and Paintshop Pro have been following its
example for the past 30 years.

[Another example: Nintendo platformers. Picture is in the margin.]

Set a good example, and people will follow it forever. I used to write
example code in a very pedagogical fashion, with outrageously long
variable names and inefficiency everywhere. I ended up having to
stop that, because users of my libs were copying and pasting my
code for use in production! Oftentimes, they would even leave the
comments!

So don't underestimate the docility of your users; they will do what
you suggest. This isn't because they're stupid. It's just that they're
Grooviness
   warning signs
Grooviness
         warning signs
  Nonsense states or representations
Grooviness
         warning signs
  Nonsense states or representations
       Invariants that aren’t
Grooviness
         warning signs
  Nonsense states or representations
       Invariants that aren’t
  Users unclear where to get started
Safety
                  “I wish it was easier to hurt myself.”
                             —Nobody, ever




Now we get to the walls.

Walls are to keep you from hurting yourself or others. Just as in the
physical metaphor, there’s a continuum between grooves and walls.
The more damage you can do with a feature, the higher the wall
should be in front of it.
Safety
                   Groove                          Wall




Now we get to the walls.

Walls are to keep you from hurting yourself or others. Just as in the
physical metaphor, there’s a continuum between grooves and walls.
The more damage you can do with a feature, the higher the wall
should be in front of it.
update things set frob=2 where frob=1;




Here is a horrible API with not enough walls. Does anyone see what’s
wrong with it? Maybe it’s bitten you in the past.
Who here has wiped out a whole DB column because they forgot the
WHERE clause?
One way to fix this might be to require you to say all if you mean
all. Might be less mathematically beautiful, but imagine the hours it
would have saved since the advent of SQL.

Here’s another old chestnut. This is a pretty common thing to do.
The trouble is—to do it, you first have to type this, which is
seriously bad news. Don’t hit Return by accident!
Maybe we could require -f for bare asterisks.
update things set frob=2 where frob=1;

    update things set frob=2;




Here is a horrible API with not enough walls. Does anyone see what’s
wrong with it? Maybe it’s bitten you in the past.
Who here has wiped out a whole DB column because they forgot the
WHERE clause?
One way to fix this might be to require you to say all if you mean
all. Might be less mathematically beautiful, but imagine the hours it
would have saved since the advent of SQL.

Here’s another old chestnut. This is a pretty common thing to do.
The trouble is—to do it, you first have to type this, which is
seriously bad news. Don’t hit Return by accident!
Maybe we could require -f for bare asterisks.
update things set frob=2 where frob=1;

    update things set frob=2;

    update things set frob=2 all;




Here is a horrible API with not enough walls. Does anyone see what’s
wrong with it? Maybe it’s bitten you in the past.
Who here has wiped out a whole DB column because they forgot the
WHERE clause?
One way to fix this might be to require you to say all if you mean
all. Might be less mathematically beautiful, but imagine the hours it
would have saved since the advent of SQL.

Here’s another old chestnut. This is a pretty common thing to do.
The trouble is—to do it, you first have to type this, which is
seriously bad news. Don’t hit Return by accident!
Maybe we could require -f for bare asterisks.
update things set frob=2 where frob=1;

    update things set frob=2;

    update things set frob=2 all;




    rm *.pyc




Here is a horrible API with not enough walls. Does anyone see what’s
wrong with it? Maybe it’s bitten you in the past.
Who here has wiped out a whole DB column because they forgot the
WHERE clause?
One way to fix this might be to require you to say all if you mean
all. Might be less mathematically beautiful, but imagine the hours it
would have saved since the advent of SQL.

Here’s another old chestnut. This is a pretty common thing to do.
The trouble is—to do it, you first have to type this, which is
seriously bad news. Don’t hit Return by accident!
Maybe we could require -f for bare asterisks.
update things set frob=2 where frob=1;

    update things set frob=2;

    update things set frob=2 all;




    rm *.pyc
    rm *




Here is a horrible API with not enough walls. Does anyone see what’s
wrong with it? Maybe it’s bitten you in the past.
Who here has wiped out a whole DB column because they forgot the
WHERE clause?
One way to fix this might be to require you to say all if you mean
all. Might be less mathematically beautiful, but imagine the hours it
would have saved since the advent of SQL.

Here’s another old chestnut. This is a pretty common thing to do.
The trouble is—to do it, you first have to type this, which is
seriously bad news. Don’t hit Return by accident!
Maybe we could require -f for bare asterisks.
update things set frob=2 where frob=1;

    update things set frob=2;

    update things set frob=2 all;




    rm *.pyc
    rm *
    rm -f *




Here is a horrible API with not enough walls. Does anyone see what’s
wrong with it? Maybe it’s bitten you in the past.
Who here has wiped out a whole DB column because they forgot the
WHERE clause?
One way to fix this might be to require you to say all if you mean
all. Might be less mathematically beautiful, but imagine the hours it
would have saved since the advent of SQL.

Here’s another old chestnut. This is a pretty common thing to do.
The trouble is—to do it, you first have to type this, which is
seriously bad news. Don’t hit Return by accident!
Maybe we could require -f for bare asterisks.
def delete(self, index, doc_type, id=None):
          """Delete a typed JSON document from a specific
          index based on its id."""




A very similar thing happened in pyelasticsearch. This is what the
delete method in pyelasticsearch used to look like. It deletes a
document based on its ID. (A document is like a row in a RDB.)

You might not be able to pick out what’s wrong unless you know ES,
but if you make a DELETE API call to it without an ID, it’ll happily
delete all documents. And id here is OPTIONAL! I can imagine
myself coding along and managing to close your parentheses early.
Or I could leave out one of the previous arguments so the value I
intended as the ID gets interpreted as one of the other arguments
instead.

So we fixed that. Here’s how it looks now.

We made ID mandatory and, to support the rare delete-all-
documents case, we added a second routine explicitly called
delete_all.
def delete(self, index, doc_type, id=None):
          """Delete a typed JSON document from a specific
          index based on its id."""




A very similar thing happened in pyelasticsearch. This is what the
delete method in pyelasticsearch used to look like. It deletes a
document based on its ID. (A document is like a row in a RDB.)

You might not be able to pick out what’s wrong unless you know ES,
but if you make a DELETE API call to it without an ID, it’ll happily
delete all documents. And id here is OPTIONAL! I can imagine
myself coding along and managing to close your parentheses early.
Or I could leave out one of the previous arguments so the value I
intended as the ID gets interpreted as one of the other arguments
instead.

So we fixed that. Here’s how it looks now.

We made ID mandatory and, to support the rare delete-all-
documents case, we added a second routine explicitly called
delete_all.
def delete(self, index, doc_type, id=None):
          """Delete a typed JSON document from a specific
          index based on its id."""




      def delete(self, index, doc_type, id):
          """Delete a typed JSON document from a specific
          index based on its id."""

      def delete_all(self, index, doc_type):
          """Delete all documents of the given doctype from
          an index."""




A very similar thing happened in pyelasticsearch. This is what the
delete method in pyelasticsearch used to look like. It deletes a
document based on its ID. (A document is like a row in a RDB.)

You might not be able to pick out what’s wrong unless you know ES,
but if you make a DELETE API call to it without an ID, it’ll happily
delete all documents. And id here is OPTIONAL! I can imagine
myself coding along and managing to close your parentheses early.
Or I could leave out one of the previous arguments so the value I
intended as the ID gets interpreted as one of the other arguments
instead.

So we fixed that. Here’s how it looks now.

We made ID mandatory and, to support the rare delete-all-
documents case, we added a second routine explicitly called
delete_all.
Error reporting: exceptions are typically safer.




The other thing to consider is how to report errors. You have 2
choices in most languages: return something, or throw an
exception. Raising an exception is typically the safer of the two, as it
reduces the chance of accidentally swallowing an error.
Safetywarning signs




In general, Ask yourself, "How can I use this to hurt myself and
others?"

People will blame themselves.
Safetywarning signs

            Surprisingly few—people will blame themselves.




In general, Ask yourself, "How can I use this to hurt myself and
others?"

People will blame themselves.
non-astronautics                         consistency



         brevity                          composability


   plain data                safety            grooviness
While you’re getting your questions together, I want to ask you one.
I feel like…

Elegance?

If you have any ideas, please do share.
non-astronautics                           consistency


        Elegance?
         brevity                          composability
                    “It is the ineffable will of Bob.”
                            —Old Thrashbarg


   plain data                 safety               grooviness
While you’re getting your questions together, I want to ask you one.
I feel like…

Elegance?

If you have any ideas, please do share.
Thank You
  Merci
  https://joind.in/7995
     twitter: ErikRose
    erik@mozilla.com

More Related Content

Similar to Fluid, Fluent APIs

Culture And Aesthetic Revisited
Culture And Aesthetic RevisitedCulture And Aesthetic Revisited
Culture And Aesthetic Revisited
Adam Keys
 

Similar to Fluid, Fluent APIs (20)

Culture And Aesthetic Revisited
Culture And Aesthetic RevisitedCulture And Aesthetic Revisited
Culture And Aesthetic Revisited
 
Apex for humans
Apex for humansApex for humans
Apex for humans
 
Ruby for Java Developers
Ruby for Java DevelopersRuby for Java Developers
Ruby for Java Developers
 
Natural Language Processing Tools for the Digital Humanities
Natural Language Processing Tools for the Digital HumanitiesNatural Language Processing Tools for the Digital Humanities
Natural Language Processing Tools for the Digital Humanities
 
Invasion of the dynamic language weenies
Invasion of the dynamic language weeniesInvasion of the dynamic language weenies
Invasion of the dynamic language weenies
 
Programming
ProgrammingProgramming
Programming
 
ppt7
ppt7ppt7
ppt7
 
ppt2
ppt2ppt2
ppt2
 
name name2 n
name name2 nname name2 n
name name2 n
 
name name2 n2
name name2 n2name name2 n2
name name2 n2
 
test ppt
test ppttest ppt
test ppt
 
name name2 n
name name2 nname name2 n
name name2 n
 
ppt21
ppt21ppt21
ppt21
 
name name2 n
name name2 nname name2 n
name name2 n
 
ppt17
ppt17ppt17
ppt17
 
ppt30
ppt30ppt30
ppt30
 
name name2 n2.ppt
name name2 n2.pptname name2 n2.ppt
name name2 n2.ppt
 
ppt18
ppt18ppt18
ppt18
 
Ruby for Perl Programmers
Ruby for Perl ProgrammersRuby for Perl Programmers
Ruby for Perl Programmers
 
ppt9
ppt9ppt9
ppt9
 

Recently uploaded

Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 

Recently uploaded (20)

A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 

Fluid, Fluent APIs

  • 1. Fluent, Fluid APIs by Erik Rose “A poet is, before anything else, a person who is passionately in love with language.” —W. H. Auden Welcome to Fluid, Fluent APIs or, perhaps more fluently, poetic APIs. My name is Erik Rose and I work at Mozilla as not a poet. Nevertheless, I’m in love with language as well—formal languages, human language, even lexically structured music and dance. Mark Twain: language is the best way of getting ideas from one head into another without surgery. So I want to tell you a story about a man who didn’t have this ability for 27 years. Nevertheless, all programmers share a love for language as well. Let me tell you a story about a man who was missing this power—and even the more basic power of human words.
  • 2. Poetic APIs by Erik Rose “A poet is, before anything else, a person who is passionately in love with language.” —W. H. Auden Welcome to Fluid, Fluent APIs or, perhaps more fluently, poetic APIs. My name is Erik Rose and I work at Mozilla as not a poet. Nevertheless, I’m in love with language as well—formal languages, human language, even lexically structured music and dance. Mark Twain: language is the best way of getting ideas from one head into another without surgery. So I want to tell you a story about a man who didn’t have this ability for 27 years. Nevertheless, all programmers share a love for language as well. Let me tell you a story about a man who was missing this power—and even the more basic power of human words.
  • 3. Poetic APIs programmer by Erik Rose ^ “A poet is, before anything else, a person who is passionately in love with language.” —W. H. Auden Welcome to Fluid, Fluent APIs or, perhaps more fluently, poetic APIs. My name is Erik Rose and I work at Mozilla as not a poet. Nevertheless, I’m in love with language as well—formal languages, human language, even lexically structured music and dance. Mark Twain: language is the best way of getting ideas from one head into another without surgery. So I want to tell you a story about a man who didn’t have this ability for 27 years. Nevertheless, all programmers share a love for language as well. Let me tell you a story about a man who was missing this power—and even the more basic power of human words.
  • 4. Ildefanso deaf hearing parents, siblings, stupid? Susan Schaller cowering mimic imaginery student pointing “Oh! Everything has a name!”
  • 5. “Oh! Everything has a name!” Now, this was a 27-year-old man who had been around. He didn’t live in the jungle. But, as Susan describes it, the look on his face was as if he had never seen a window before. The window became a different thing by having a symbol attached to it.
  • 6. “The window became a different thing by having a symbol attached to it.” He had a category for it. He could talk about a window to someone.
  • 7. Inventing Language “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” —Martin Fowler In creating an API, your task is not only to make something that works— but to put your categories for the universe into somebody else's head— to choose symbols that all discourse will be in terms of. As you do so, take care, because: limits the thoughts users will be able to have about the problem domain. Ildefanso trouble w/proper names & time “When did we last meet?” → “In the dry season” or “At the holidays”. The abstract names and numbers we give to various divisions of time aren’t the abstractions he’s built his world on. Before he had lang, he could do simple math with quantites up to about 10, but he couldn’t get much farther until he had words. Likewise, when you make an API, opportunity to succeed: magnifying efforts of all those who come after you fail: frustrating all those who come after you Example: urllib2 vs. Requests
  • 8. req = urllib2.Request(gh_url) password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() password_manager.add_password( None, 'https://api.github.com', 'user', 'pass') auth_manager = urllib2.HTTPBasicAuthHandler(password_manager) opener = urllib2.build_opener(auth_manager) urllib2.install_opener(opener) handler = urllib2.urlopen(req) print handler.getcode() urllib2: stdlib, horrendously verbose, hard to do common things (partially because it’s not just for HTTP) Requests: 3rd-party, de facto standard because it harnesses the common concept of an HTTP get—productivity multiplier This is why API design makes greatest demands on fluency: You're drawing out borders around concepts which have to fit a variety of other people’s brains and be adaptable enough for situations you haven’t forseen. I’m going to give you 7 rules of thumb to increase your chances. The first one is…
  • 9. req = urllib2.Request(gh_url) password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() password_manager.add_password( None, 'https://api.github.com', 'user', 'pass') auth_manager = urllib2.HTTPBasicAuthHandler(password_manager) opener = urllib2.build_opener(auth_manager) urllib2.install_opener(opener) handler = urllib2.urlopen(req) print handler.getcode() r = requests.get('https://api.github.com', auth=('user', 'pass')) print r.status_code urllib2: stdlib, horrendously verbose, hard to do common things (partially because it’s not just for HTTP) Requests: 3rd-party, de facto standard because it harnesses the common concept of an HTTP get—productivity multiplier This is why API design makes greatest demands on fluency: You're drawing out borders around concepts which have to fit a variety of other people’s brains and be adaptable enough for situations you haven’t forseen. I’m going to give you 7 rules of thumb to increase your chances. The first one is…
  • 10. Don’t Be An Architecture Astronaut My first step when designing a new library is actually to not design a new library. It’s all well and good to seek to do good design in the abstract, but design is basically imagination, and imagination is essentially hypothesizing, and we know from history how well hypotheses do when they’re not continually tested and brought back to earth by reality. You end up with alchemy rather than chemistry.
  • 11. Very impressive to look at and academically interesting but it’s merely a self-consistent system of nonsense, not anything of practical use. So, to bring our efforts back into the realm of science, remember that the best libraries are extracted, not invented.
  • 12. The best libraries are extracted, not invented. What this means is that you should have an application that already does the things you want your library to do. [Preferably 2—and as different as possible.] And then take the bits of them that do the things you’re interested in and factor them up.
  • 13. For example, I wrote a plugin nose-progressive for the popular Python test framework nose, and its purpose was to display a progress bar and just generally make output easier to follow. It made use of a lot of terminal escape codes, for things like bolding, coloring, and positioning. The code looked something like this.
  • 14. For example, I wrote a plugin nose-progressive for the popular Python test framework nose, and its purpose was to display a progress bar and just generally make output easier to follow. It made use of a lot of terminal escape codes, for things like bolding, coloring, and positioning. The code looked something like this.
  • 15. def terminal_codes(self, stream): capabilities = ['bold', 'sc', 'rc', 'sgr0', 'el'] if hasattr(stream, 'fileno') and isatty(stream.fileno()): # Explicit args make setupterm() work even when -s is passed: setupterm(None, stream.fileno()) # so things like tigetstr() work codes = dict((x, tigetstr(x)) for x in capabilities) cup = tigetstr('cup') codes['cup'] = lambda line, column: tparm(cup, line, column) else: # If you're crazy enough to pipe this to a file or something, # don't output terminal codes: codes = defaultdict(lambda: '', cup=lambda line, column: '') return codes ... self._codes = terminal_codes(stdout) ... class AtLine(object): def __enter__(self): """Save position and move to progress bar, col 1.""" self.stream.write(self._codes['sc']) # save position self.stream.write(self._codes['cup'](self.line, 0)) def __exit__(self, type, value, tb): self.stream.write(self._codes['rc']) # restore position ... print self._codes['bold'] + results + self._codes['sgr0'] Lots of terminal setup and bookkeeping all intertwined with code that actually kept track of tests. Raw terminal capability names like sgr0 and rc that I had to keep looking up. [TODO: red underlines] Every time I’d have to do some formatting I’d think “Geez, I wish there were a decent abstraction around this so I wouldn’t have to look at it.” It’s at this point—when you have a real, useful program with a library struggling to get out—that you can come down from orbit and start thinking about library design. So it was time for blessings—my terminal formatting library—to be born.
  • 16. Tasks The first thing to do is to dump everything out on the workbench. What sort of things ought the library to do? bam bam Now, what tools have we got at our workbench? Well…all the stuff in our language…
  • 17. Tasks Print some text with formatting. The first thing to do is to dump everything out on the workbench. What sort of things ought the library to do? bam bam Now, what tools have we got at our workbench? Well…all the stuff in our language…
  • 18. Tasks Print some text with formatting. Print some text at a location, then snap back. The first thing to do is to dump everything out on the workbench. What sort of things ought the library to do? bam bam Now, what tools have we got at our workbench? Well…all the stuff in our language…
  • 19. Language Constructs Functions, arguments, keyword arguments Decorators Context managers Classes (really more of an implementation detail) bam bam bam bam common patterns…: bam bam bam Then we shake the workbench up and see if we can find some good pairings between tools and tasks. What guides you? Consistency.
  • 20. Language Constructs Functions, arguments, keyword arguments Decorators Context managers Classes (really more of an implementation detail) å Patterns, Protocols, Interfaces, and Conventions Sequences Iterators Mappings bam bam bam bam common patterns…: bam bam bam Then we shake the workbench up and see if we can find some good pairings between tools and tasks. What guides you? Consistency.
  • 21. Consistency “Think like a wise man, but communicate in the language of the people.” —William Butler Yeats You have the entire technical and cultural weight of the language itself behind you as well as accumulated habits of influential libs and the programmer community. When we're designing web sites, we think of it this way: people spend 90% of their time on other people's web sites, so… make the logo link to the home page put the log-in link in the upper right and so on When writing an API, realize: users will spend 90% time using other APIs You can be weird and clever if you need to be, but if you can manage to stick with conventions, you’ll get these bonuses… 1. conveys respect for your users. You're not some outsider who is going to make their code a mishmash of clashing styles. I can't tell you how many times I've encountered a Java API masquerading as Python one and thrown it away in disgust. getters and setters + violation of case conventions → I'm going to move on.
  • 22. Brilliant thing about early Mac project: explicit HIGs Every program same commands: open, close, quit, copy, paste. In same menus w/same keyboard shortcuts. Learn one program → learned 'em all If you implement get(key, default) and call it that, people will pick it up faster and remember it better than fetch(default, key). 3. Polymorphism. Can use calling code on dicts or whatever your class is.
  • 23. get(key, default) > fetch(default, key) Brilliant thing about early Mac project: explicit HIGs Every program same commands: open, close, quit, copy, paste. In same menus w/same keyboard shortcuts. Learn one program → learned 'em all If you implement get(key, default) and call it that, people will pick it up faster and remember it better than fetch(default, key). 3. Polymorphism. Can use calling code on dicts or whatever your class is.
  • 24. Tasks Print some text at a location, then snap back. Print some text with formatting. So, coming back to our tasks at hand, how do we bang those tasks against existing language features and conventions? Well, I like to get as many alternatives out on the workbench as possible. I call these sketches. Really only 2 choices for printing at a location. Both are okay, but the 2nd gives us the flexibility to put multiple statements inside—even calls out to other functions. And then the first can be written in terms of it, if you like.
  • 25. Tasks Print some text at a location, then snap back. Print some text with formatting. print_at('Hello world', 10, 2) with location(10, 2): print 'Hello world' for thing in range(10): print thing So, coming back to our tasks at hand, how do we bang those tasks against existing language features and conventions? Well, I like to get as many alternatives out on the workbench as possible. I call these sketches. Really only 2 choices for printing at a location. Both are okay, but the 2nd gives us the flexibility to put multiple statements inside—even calls out to other functions. And then the first can be written in terms of it, if you like.
  • 26. Tasks Print some text at a location, then snap back. Print some text with formatting. print_formatted('Hello world', 'red', 'bold') print color('Hello world', 'red', 'bold') print color('<red><bold>Hello world</bold></red>') print red(bold('Hello') + 'world') print codes['red'] + codes['bold'] + 'Hello world' + codes['normal'] print '{t.red}Hi{t.bold}Mom{t.normal}'.format(t=terminal) print terminal.red_bold + 'Hello world' + terminal.normal Here are sketches for text formatting. We see some square brackets here, some dots, some string concatenation, some function calls with kwargs and with positional args. Most separate the printing from the formating, but one combines them. I have a couple of favorites here. this…common function call syntax…composable. It frankly looks like HTML. trouble: no portable definition of a "turn [bold, for example] off" escape code, so state have to embed it in the string or in a parallel data structure -> enormous code complexity & mental load for users went with attrs ...we save a great deal of complexity—both internally and for the caller Consistent: use with Python’s built-in templating language: less to
  • 27. Tasks Print some text at a location, then snap back. Print some text with formatting. print_formatted('Hello world', 'red', 'bold') print color('Hello world', 'red', 'bold') print color('<red><bold>Hello world</bold></red>') print red(bold('Hello') + 'world') print codes['red'] + codes['bold'] + 'Hello world' + codes['normal'] print '{t.red}Hi{t.bold}Mom{t.normal}'.format(t=terminal) print terminal.red_bold + 'Hello world' + terminal.normal Here are sketches for text formatting. We see some square brackets here, some dots, some string concatenation, some function calls with kwargs and with positional args. Most separate the printing from the formating, but one combines them. I have a couple of favorites here. this…common function call syntax…composable. It frankly looks like HTML. trouble: no portable definition of a "turn [bold, for example] off" escape code, so state have to embed it in the string or in a parallel data structure -> enormous code complexity & mental load for users went with attrs ...we save a great deal of complexity—both internally and for the caller Consistent: use with Python’s built-in templating language: less to
  • 28. Tasks Print some text at a location, then snap back. Print some text with formatting. print_formatted('Hello world', 'red', 'bold') print color('Hello world', 'red', 'bold') print color('<red><bold>Hello world</bold></red>') print red(bold('Hello') + 'world') print codes['red'] + codes['bold'] + 'Hello world' + codes['normal'] print '{t.red}Hi{t.bold}Mom{t.normal}'.format(t=terminal) print terminal.red_bold + 'Hello world' + terminal.normal Here are sketches for text formatting. We see some square brackets here, some dots, some string concatenation, some function calls with kwargs and with positional args. Most separate the printing from the formating, but one combines them. I have a couple of favorites here. this…common function call syntax…composable. It frankly looks like HTML. trouble: no portable definition of a "turn [bold, for example] off" escape code, so state have to embed it in the string or in a parallel data structure -> enormous code complexity & mental load for users went with attrs ...we save a great deal of complexity—both internally and for the caller Consistent: use with Python’s built-in templating language: less to
  • 29. from blessings import Terminal t = Terminal() print t.red_bold + 'Hello world' + t.normal print t.red_on_white + 'Hello world' + t.normal print t.underline_red_on_green + 'Hello world' + t.normal But, if you’re going to do something a little weird, at least be self- consistent. This mashed-together syntax is used for everything in Blessings: formatting, foreground color, background color. It’s hard to get wrong; you can put the formatters in any order, and it just works. Thus, the user doesn’t have to keep making trips back to the docs. They internalize one slightly odd idea, and that carries them through all their use cases.
  • 30. Consistency warning signs So when you’re evaluating your consistency, look out for these red flags: Frequent refs to your own docs or source code as you’re building it. (“Hmm, what did I call that?” “What order does it go in?”) ! Example: "index" kwarg vs. "indexes" in pyelasticsearch Feeling like you’re inventing novel syntax. Use judgment: designs paying off rather than showing off.
  • 31. Consistency warning signs Frequent references to your docs or source So when you’re evaluating your consistency, look out for these red flags: Frequent refs to your own docs or source code as you’re building it. (“Hmm, what did I call that?” “What order does it go in?”) ! Example: "index" kwarg vs. "indexes" in pyelasticsearch Feeling like you’re inventing novel syntax. Use judgment: designs paying off rather than showing off.
  • 32. Consistency warning signs Frequent references to your docs or source Feeling syntactically clever So when you’re evaluating your consistency, look out for these red flags: Frequent refs to your own docs or source code as you’re building it. (“Hmm, what did I call that?” “What order does it go in?”) ! Example: "index" kwarg vs. "indexes" in pyelasticsearch Feeling like you’re inventing novel syntax. Use judgment: designs paying off rather than showing off.
  • 33. Brevity “The finest language is mostly made up of simple, unimposing words.” —George Eliot 3rd rule of thumb is Brevity. Make common things… short (in tokens, sometimes even in terms of chars if it doesn’t hurt understanding)
  • 34. from blessings import Terminal term = Terminal() print term.bold + 'I am bold!' + term.normal excerpt from earlier now you’ll see why (mashed-together) Since formatting something and then resetting to plain text is the most common operation, we make it the shortest. Not everything has to be brief. Another way to think about it: pay for what you eat.
  • 35. from blessings import Terminal term = Terminal() print term.bold + 'I am bold!' + term.normal print term.bold('I am bold!') excerpt from earlier now you’ll see why (mashed-together) Since formatting something and then resetting to plain text is the most common operation, we make it the shortest. Not everything has to be brief. Another way to think about it: pay for what you eat.
  • 36. from blessings import Terminal term = Terminal() print term.bold + 'I am bold!' + term.normal print term.bold('I am bold!') print '{t.bold}Very {t.red}emphasized{t.normal}'.format(t=term) excerpt from earlier now you’ll see why (mashed-together) Since formatting something and then resetting to plain text is the most common operation, we make it the shortest. Not everything has to be brief. Another way to think about it: pay for what you eat.
  • 38. Brevity warning signs Copying and pasting when writing against your API
  • 39. Brevity warning signs Copying and pasting when writing against your API Typing something irrelevant while grumbling “Why can’t it just assume the obvious thing?”
  • 40. Composability “Perfection is achieved not when there is nothing left to add but when there is nothing left to take away.” —Antoine de Saint-Exupery Making your abstractions composable means being able to reuse them in many different situations and plug them together in different ways. Million ways to say this: flexibility, loose coupling, small pieces loosely joined. It all comes down to the minimization of assumptions. There are two ways you can do this, one of them wrong.
  • 41. Let’s go back to another way we could have done Blessings. Jeff Quast put together a really neat BBS called x84. It works over telnet using Blessings. Multiprocess design. Most of the action, including formatting, happens in parent process, and children handle connections. With the above, there’s no way to pass the output to the children, because we assumed (there’s that coupling) we’d be formatting and printing all in one go. The wrong way to solve this is to add an option. Print to some file handle. But then you have to set up a file handle, even if you just want to keep a string in memory. Plus it makes the API more complicated: you have to document the option, and there’s a branch in the code for it. Then you have to test it. Every time I think about adding an option, I look around for any other escape route first. Better way: break that coupling.
  • 42. print_formatted('Hello world', 'red', 'bold') Let’s go back to another way we could have done Blessings. Jeff Quast put together a really neat BBS called x84. It works over telnet using Blessings. Multiprocess design. Most of the action, including formatting, happens in parent process, and children handle connections. With the above, there’s no way to pass the output to the children, because we assumed (there’s that coupling) we’d be formatting and printing all in one go. The wrong way to solve this is to add an option. Print to some file handle. But then you have to set up a file handle, even if you just want to keep a string in memory. Plus it makes the API more complicated: you have to document the option, and there’s a branch in the code for it. Then you have to test it. Every time I think about adding an option, I look around for any other escape route first. Better way: break that coupling.
  • 43. print_formatted('Hello world', 'red', 'bold') print_formatted('Hello world', 'red', 'bold', out=some_file) Let’s go back to another way we could have done Blessings. Jeff Quast put together a really neat BBS called x84. It works over telnet using Blessings. Multiprocess design. Most of the action, including formatting, happens in parent process, and children handle connections. With the above, there’s no way to pass the output to the children, because we assumed (there’s that coupling) we’d be formatting and printing all in one go. The wrong way to solve this is to add an option. Print to some file handle. But then you have to set up a file handle, even if you just want to keep a string in memory. Plus it makes the API more complicated: you have to document the option, and there’s a branch in the code for it. Then you have to test it. Every time I think about adding an option, I look around for any other escape route first. Better way: break that coupling.
  • 44. print_formatted('Hello world', 'red', 'bold') print_formatted('Hello world', 'red', 'bold', out=some_file) print formatted('Hello world', 'red', 'bold') Let’s go back to another way we could have done Blessings. Jeff Quast put together a really neat BBS called x84. It works over telnet using Blessings. Multiprocess design. Most of the action, including formatting, happens in parent process, and children handle connections. With the above, there’s no way to pass the output to the children, because we assumed (there’s that coupling) we’d be formatting and printing all in one go. The wrong way to solve this is to add an option. Print to some file handle. But then you have to set up a file handle, even if you just want to keep a string in memory. Plus it makes the API more complicated: you have to document the option, and there’s a branch in the code for it. Then you have to test it. Every time I think about adding an option, I look around for any other escape route first. Better way: break that coupling.
  • 45. Composabilty warning signs Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 46. Composabilty warning signs Classes with lots of state Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 47. Composabilty warning signs Classes with lots of state class ElasticSearch(object): """ An object which manages connections to elasticsearch and acts as a go-between for API calls to it""" def index(self, index, doc_type, doc, id=None, force_insert=False, query_params=None): """Put a typed JSON document into a specific index to make it searchable.""" def search(self, query, **kwargs): """Execute a search query against one or more indices and get back search hits.""" def more_like_this(self, index, doc_type, id, mlt_fields, body='', query_params=None): """Execute a "more like this" search query against one or more fields and get back search hits.""" . . . Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 48. Composabilty warning signs Classes with lots of state class ElasticSearch(object): """ class PenaltyBox(object): An object which manages connections to elasticsearch and acts as a """A thread-safe bucket of servers (or other things) that may have go-between for API calls to it""" downtime.""" def index(self, index, doc_type, doc, id=None, force_insert=False, query_params=None): def get(self): """Put a typed JSON document into a specific index server and a bool indicating whether it was from the """Return a random to make it searchable.""" dead list.""" def search(self, query, **kwargs): mark_dead(self, server): def """Execute a search query against one or more indices and get won't search """Guarantee that this server back be returned again until a period of hits.""" time has passed, unless all servers are dead.""" def more_like_this(self, index, def mark_live(self, server): doc_type, id, mlt_fields, body='', query_params=None): """Execute a "more like this" search query against one or dead list to and live one.""" """Move a server from the more fields the get back search hits.""" . . . Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 49. Composabilty warning signs Classes with lots of state Deep inheritance hierarchies Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 50. Composabilty warning signs Classes with lots of state Deep inheritance hierarchies Violations of the Law of Demeter Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 51. Composabilty warning signs Classes with lots of state Deep inheritance hierarchies Violations of the Law of Demeter Mocking in tests Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 52. Composabilty warning signs Classes with lots of state Deep inheritance hierarchies Violations of the Law of Demeter Mocking in tests print_formatted('Hello world', 'red', 'bold') Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 53. Composabilty warning signs Classes with lots of state Deep inheritance hierarchies Violations of the Law of Demeter Mocking in tests print_formatted('Hello world', 'red', 'bold') print formatted('Hello world', 'red', 'bold') Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 54. Composabilty warning signs Classes with lots of state Deep inheritance hierarchies Violations of the Law of Demeter Mocking in tests Options print_formatted('Hello world', 'red', 'bold') print formatted('Hello world', 'red', 'bold') Watch out for these red flags: 1. Classes with lots of state make me think there are multiple little classes hiding inside, each serving a single purpose. For example, pyelasticsearch connection object…needed penalty box for downed nodes…tempting to add directly…better to separate…now can reuse in other projects. Doesn’t even know about connections—just strings—so represent whatever you want. 2. Deep inheritance hierarchies. People say “composition is better than inheritance”, and here’s why. If you inherit, your object gets easy access to the functionality of the parent—true. But it also inherits all the invariant baggage of its direct parent and all the parents up the line. It’s required to maintain those invariants, but it doesn’t get any help from the language, which say it’s perfectly welcome to muck with private vars however it likes. Subclassing just increases the amount of code that has to tiptoe around a collection of state. Instead it’s often better to instantiate the object you need inside yours and stick it in an instance var.
  • 55. Plain Data “All the great things are simple, and many can be expressed in a single word…” —Sir Winston Churchill Another rule of thumb that helps with reusability is plain data. Whenever possible, your API should speak with its callers using simple, built-in types, not big hairy data structures. The aim is to reduce barriers to reuse. What’s a barrier look like?
  • 56. It looks like the ConfigParser class out of the Python stdlib. Here’s an excerpt of its API. Its purpose in life is to read INI files. Unfortunately, it’s not just a parser that does its jobs and translates the parsed content into something more usable, like a dictionary. Instead, the config ends up represented as this unwieldy ConfigParser object with lots of custom-named methods that don’t conform to any Python idiom. This means that any code that uses ConfigParser ends up written in its terms. And, if you want the flexibility to pull in configuration from some source that isn’t an INI file, you’ll have to write your own abstraction layer on top of ConfigParser. I’m going to continue to pick on ConfigParser (and pretend it has a slightly worse API for illustrative purposes). (Actually takes multiple filenames and does so for a fairly good reason.) But let’s pretend it takes a single config file name. This might feel like a convenience, since config often comes from files, but it’s really a barrier.
  • 57. ✚ It looks like the ConfigParser class out of the Python stdlib. Here’s an excerpt of its API. Its purpose in life is to read INI files. Unfortunately, it’s not just a parser that does its jobs and translates the parsed content into something more usable, like a dictionary. Instead, the config ends up represented as this unwieldy ConfigParser object with lots of custom-named methods that don’t conform to any Python idiom. This means that any code that uses ConfigParser ends up written in its terms. And, if you want the flexibility to pull in configuration from some source that isn’t an INI file, you’ll have to write your own abstraction layer on top of ConfigParser. I’m going to continue to pick on ConfigParser (and pretend it has a slightly worse API for illustrative purposes). (Actually takes multiple filenames and does so for a fairly good reason.) But let’s pretend it takes a single config file name. This might feel like a convenience, since config often comes from files, but it’s really a barrier.
  • 58. ✚ It looks like the ConfigParser class out of the Python stdlib. Here’s an excerpt of its API. Its purpose in life is to read INI files. Unfortunately, it’s not just a parser that does its jobs and translates the parsed content into something more usable, like a dictionary. Instead, the config ends up represented as this unwieldy ConfigParser object with lots of custom-named methods that don’t conform to any Python idiom. This means that any code that uses ConfigParser ends up written in its terms. And, if you want the flexibility to pull in configuration from some source that isn’t an INI file, you’ll have to write your own abstraction layer on top of ConfigParser. I’m going to continue to pick on ConfigParser (and pretend it has a slightly worse API for illustrative purposes). (Actually takes multiple filenames and does so for a fairly good reason.) But let’s pretend it takes a single config file name. This might feel like a convenience, since config often comes from files, but it’s really a barrier.
  • 59. ✚ It looks like the ConfigParser class out of the Python stdlib. Here’s an excerpt of its API. Its purpose in life is to read INI files. Unfortunately, it’s not just a parser that does its jobs and translates the parsed content into something more usable, like a dictionary. Instead, the config ends up represented as this unwieldy ConfigParser object with lots of custom-named methods that don’t conform to any Python idiom. This means that any code that uses ConfigParser ends up written in its terms. And, if you want the flexibility to pull in configuration from some source that isn’t an INI file, you’ll have to write your own abstraction layer on top of ConfigParser. I’m going to continue to pick on ConfigParser (and pretend it has a slightly worse API for illustrative purposes). (Actually takes multiple filenames and does so for a fairly good reason.) But let’s pretend it takes a single config file name. This might feel like a convenience, since config often comes from files, but it’s really a barrier.
  • 60. ✚ RawConfigParser.parse(string) It looks like the ConfigParser class out of the Python stdlib. Here’s an excerpt of its API. Its purpose in life is to read INI files. Unfortunately, it’s not just a parser that does its jobs and translates the parsed content into something more usable, like a dictionary. Instead, the config ends up represented as this unwieldy ConfigParser object with lots of custom-named methods that don’t conform to any Python idiom. This means that any code that uses ConfigParser ends up written in its terms. And, if you want the flexibility to pull in configuration from some source that isn’t an INI file, you’ll have to write your own abstraction layer on top of ConfigParser. I’m going to continue to pick on ConfigParser (and pretend it has a slightly worse API for illustrative purposes). (Actually takes multiple filenames and does so for a fairly good reason.) But let’s pretend it takes a single config file name. This might feel like a convenience, since config often comes from files, but it’s really a barrier.
  • 61. ✚ RawConfigParser.parse(string) charming_parser.parse(file_contents('some_file.ini')) It looks like the ConfigParser class out of the Python stdlib. Here’s an excerpt of its API. Its purpose in life is to read INI files. Unfortunately, it’s not just a parser that does its jobs and translates the parsed content into something more usable, like a dictionary. Instead, the config ends up represented as this unwieldy ConfigParser object with lots of custom-named methods that don’t conform to any Python idiom. This means that any code that uses ConfigParser ends up written in its terms. And, if you want the flexibility to pull in configuration from some source that isn’t an INI file, you’ll have to write your own abstraction layer on top of ConfigParser. I’m going to continue to pick on ConfigParser (and pretend it has a slightly worse API for illustrative purposes). (Actually takes multiple filenames and does so for a fairly good reason.) But let’s pretend it takes a single config file name. This might feel like a convenience, since config often comes from files, but it’s really a barrier.
  • 62. class Node(dict): """A wrapper around a native Reflect.parse dict providing some convenience methods and some caching of expensive computations""" Even when you have an inherently more complicated data structure, expose as much of it as possible as built-in interfaces. Spiderflunky…doubly-linked tree…transform to iterator: walk_up and walk_down. Sexy list comps Notice what this has done: it’s forced the caller to implement their stuff in terms of your interface. Now they’re stuck with your library forever, unless they want to rewrite their code. Why not meet on common turf, like this? (Just return a map, or at least something that acts like one.) This is about the language that your API speaks with its callers. You have a couple of choices here: You can speak their language, or you can force them to speak yours.
  • 63. class Node(dict): """A wrapper around a native Reflect.parse dict providing some convenience methods and some caching of expensive computations""" def walk_up(self): """Yield each node from here to the root of the tree, starting with myself.""" node = self while node: yield node node = node.get('_parent') Even when you have an inherently more complicated data structure, expose as much of it as possible as built-in interfaces. Spiderflunky…doubly-linked tree…transform to iterator: walk_up and walk_down. Sexy list comps Notice what this has done: it’s forced the caller to implement their stuff in terms of your interface. Now they’re stuck with your library forever, unless they want to rewrite their code. Why not meet on common turf, like this? (Just return a map, or at least something that acts like one.) This is about the language that your API speaks with its callers. You have a couple of choices here: You can speak their language, or you can force them to speak yours.
  • 64. class Node(dict): """A wrapper around a native Reflect.parse dict providing some convenience methods and some caching of expensive computations""" def walk_up(self): """Yield each node from here to the root of the tree, starting with myself.""" node = self while node: yield node node = node.get('_parent') def nearest_scope_holder(self): """Return the nearest node that can have its own scope, potentially including myself. This will be either a FunctionDeclaration or a Program (for now). """ return first(n for n in self.walk_up() if n['type'] in ['FunctionDeclaration', 'Program']) Even when you have an inherently more complicated data structure, expose as much of it as possible as built-in interfaces. Spiderflunky…doubly-linked tree…transform to iterator: walk_up and walk_down. Sexy list comps Notice what this has done: it’s forced the caller to implement their stuff in terms of your interface. Now they’re stuck with your library forever, unless they want to rewrite their code. Why not meet on common turf, like this? (Just return a map, or at least something that acts like one.) This is about the language that your API speaks with its callers. You have a couple of choices here: You can speak their language, or you can force them to speak yours.
  • 65. Plain Data warning signs (exception: inversion of control. Data, not control.)
  • 66. Plain Data warning signs Users immediately transforming your output to another format (exception: inversion of control. Data, not control.)
  • 67. Plain Data warning signs Users immediately transforming your output to another format Instantiating one object just to pass it to another (exception: inversion of control. Data, not control.)
  • 68. Grooviness “The bad teacher’s words fall on his pupils like harsh rain; the good teacher’s, as gently as dew.” —Talmud: Ta’anith 7b I have this idea that users of my APIs should spend most of their time “in the groove.” And I don’t just mean performing well or doing their work easily. I mean it in a more physical sense. Think of programming as walking across a plane. The groove is a nice, smooth path where it’s easy to walk. You could even walk it with your eyes closed, because the sloping sides would guide you gently back to the center. Note that there’s nothing keeping you from stepping out of the groove and walking somewhere else if you like; it’s not a wall you have to vault over—we’ll get to those in a minute. But the groove is very attractive, and new users are drawn there first. Grooves are about guidance, not limitations. Here are some ideas on how to cut grooves in your APIs.
  • 69. Grooviness Groove Wall I have this idea that users of my APIs should spend most of their time “in the groove.” And I don’t just mean performing well or doing their work easily. I mean it in a more physical sense. Think of programming as walking across a plane. The groove is a nice, smooth path where it’s easy to walk. You could even walk it with your eyes closed, because the sloping sides would guide you gently back to the center. Note that there’s nothing keeping you from stepping out of the groove and walking somewhere else if you like; it’s not a wall you have to vault over—we’ll get to those in a minute. But the groove is very attractive, and new users are drawn there first. Grooves are about guidance, not limitations. Here are some ideas on how to cut grooves in your APIs.
  • 70. Avoid nonsense representations. Avoid nonsense representations I maintain a Python API for elasticsearch called pyelasticsearch. There are 2 ways to query it: JSON and simpler string syntax. There’s no such thing as a query that uses both formats at once; you have to choose one.
  • 71. Avoid nonsense representations. { "bool" : { "must" : { "term" : { "user" : "fred" } }, "must_not" : { "range" : { "age" : { "from" : 12, "to" : 21 } } }, "minimum_number_should_match" : 1, "boost" : 1.0 } } Avoid nonsense representations I maintain a Python API for elasticsearch called pyelasticsearch. There are 2 ways to query it: JSON and simpler string syntax. There’s no such thing as a query that uses both formats at once; you have to choose one.
  • 72. Avoid nonsense representations. { "bool" : { "must" : { "term" : { "user" : "fred" } }, "must_not" : { "range" : { "age" : { "from" : 12, "to" : 21 } } }, "minimum_number_should_match" : 1, "boost" : 1.0 } } frob*ator AND snork Avoid nonsense representations I maintain a Python API for elasticsearch called pyelasticsearch. There are 2 ways to query it: JSON and simpler string syntax. There’s no such thing as a query that uses both formats at once; you have to choose one.
  • 73. Old pyelasticsearch def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" Here’s what pyelasticsearch used to look like. All its args were optional. You’d pass string-style queries in using the q argument and JSON-style queries using the body one. Of course, this meant nothing stopped you from doing this or even this. But those are nonsense. You shouldn’t even be able to get that far, not to mention the code that has to exist in your library to check for and reject such silliness. New pyelasticsearch replaces that mess with a single required argument to which you can pass either a string or a JSON dictionary. You can’t go wrong, because the interpreter won’t let you. Much groovier! Incidentally, this turns up another principle: fail shallowly. In languages that give you tracebacks, this helps. If, heaven forbid, you’re using a language that doesn’t, this is vital.
  • 74. Old pyelasticsearch def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" search(q='frob*ator AND snork', body={'some': 'query'}) Here’s what pyelasticsearch used to look like. All its args were optional. You’d pass string-style queries in using the q argument and JSON-style queries using the body one. Of course, this meant nothing stopped you from doing this or even this. But those are nonsense. You shouldn’t even be able to get that far, not to mention the code that has to exist in your library to check for and reject such silliness. New pyelasticsearch replaces that mess with a single required argument to which you can pass either a string or a JSON dictionary. You can’t go wrong, because the interpreter won’t let you. Much groovier! Incidentally, this turns up another principle: fail shallowly. In languages that give you tracebacks, this helps. If, heaven forbid, you’re using a language that doesn’t, this is vital.
  • 75. Old pyelasticsearch def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" search(q='frob*ator AND snork', body={'some': 'query'}) search() Here’s what pyelasticsearch used to look like. All its args were optional. You’d pass string-style queries in using the q argument and JSON-style queries using the body one. Of course, this meant nothing stopped you from doing this or even this. But those are nonsense. You shouldn’t even be able to get that far, not to mention the code that has to exist in your library to check for and reject such silliness. New pyelasticsearch replaces that mess with a single required argument to which you can pass either a string or a JSON dictionary. You can’t go wrong, because the interpreter won’t let you. Much groovier! Incidentally, this turns up another principle: fail shallowly. In languages that give you tracebacks, this helps. If, heaven forbid, you’re using a language that doesn’t, this is vital.
  • 76. Old pyelasticsearch def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" search(q='frob*ator AND snork', body={'some': 'query'}) search() ✚ Here’s what pyelasticsearch used to look like. All its args were optional. You’d pass string-style queries in using the q argument and JSON-style queries using the body one. Of course, this meant nothing stopped you from doing this or even this. But those are nonsense. You shouldn’t even be able to get that far, not to mention the code that has to exist in your library to check for and reject such silliness. New pyelasticsearch replaces that mess with a single required argument to which you can pass either a string or a JSON dictionary. You can’t go wrong, because the interpreter won’t let you. Much groovier! Incidentally, this turns up another principle: fail shallowly. In languages that give you tracebacks, this helps. If, heaven forbid, you’re using a language that doesn’t, this is vital.
  • 77. Old pyelasticsearch def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" search(q='frob*ator AND snork', body={'some': 'query'}) search() ✚ New pyelasticsearch def search(self, query, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" Here’s what pyelasticsearch used to look like. All its args were optional. You’d pass string-style queries in using the q argument and JSON-style queries using the body one. Of course, this meant nothing stopped you from doing this or even this. But those are nonsense. You shouldn’t even be able to get that far, not to mention the code that has to exist in your library to check for and reject such silliness. New pyelasticsearch replaces that mess with a single required argument to which you can pass either a string or a JSON dictionary. You can’t go wrong, because the interpreter won’t let you. Much groovier! Incidentally, this turns up another principle: fail shallowly. In languages that give you tracebacks, this helps. If, heaven forbid, you’re using a language that doesn’t, this is vital.
  • 78. Old pyelasticsearch def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" search(q='frob*ator AND snork', body={'some': 'query'}) search() ✚ New pyelasticsearch def search(self, query, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results.""" Fail shallowly. Here’s what pyelasticsearch used to look like. All its args were optional. You’d pass string-style queries in using the q argument and JSON-style queries using the body one. Of course, this meant nothing stopped you from doing this or even this. But those are nonsense. You shouldn’t even be able to get that far, not to mention the code that has to exist in your library to check for and reject such silliness. New pyelasticsearch replaces that mess with a single required argument to which you can pass either a string or a JSON dictionary. You can’t go wrong, because the interpreter won’t let you. Much groovier! Incidentally, this turns up another principle: fail shallowly. In languages that give you tracebacks, this helps. If, heaven forbid, you’re using a language that doesn’t, this is vital.
  • 79. Resource acquisition is initialization. Another way to provide grooves is RAII. Here’s a poppable balloon class. But, as you can see, when you first construct one, it isn’t poppable. You can pop a balloon that has no air in it. You might imagine the documentation says “Before popping the balloon, fill it with the desired amount of air.” That’s broken design. A poppable balloon should be poppable, full-stop. The user shouldn’t have to head into the docs to figure out what’s wrong. A better way is to take the initial fill amount at construction. The user is required to provide it and gets a prompt error if he doesn’t, so there’s no wondering later why the balloon didn’t make any noise when you prick it. RAII is a more specific case of “Don’t have invariants that aren’t.”
  • 80. Resource acquisition is initialization. class PoppableBalloon(object): """A balloon you can pop""" def __init__(self): self.air = 0 def fill(self, how_much): self.air = how_much Another way to provide grooves is RAII. Here’s a poppable balloon class. But, as you can see, when you first construct one, it isn’t poppable. You can pop a balloon that has no air in it. You might imagine the documentation says “Before popping the balloon, fill it with the desired amount of air.” That’s broken design. A poppable balloon should be poppable, full-stop. The user shouldn’t have to head into the docs to figure out what’s wrong. A better way is to take the initial fill amount at construction. The user is required to provide it and gets a prompt error if he doesn’t, so there’s no wondering later why the balloon didn’t make any noise when you prick it. RAII is a more specific case of “Don’t have invariants that aren’t.”
  • 81. Resource acquisition is initialization. class PoppableBalloon(object): """A balloon you can pop""" def __init__(self): self.air = 0 def fill(self, how_much): self.air = how_much class PoppableBalloon(object): """A balloon you can pop""" def __init__(self, initial_fill): self.air = initial_fill def fill(self, how_much): self.air = how_much Another way to provide grooves is RAII. Here’s a poppable balloon class. But, as you can see, when you first construct one, it isn’t poppable. You can pop a balloon that has no air in it. You might imagine the documentation says “Before popping the balloon, fill it with the desired amount of air.” That’s broken design. A poppable balloon should be poppable, full-stop. The user shouldn’t have to head into the docs to figure out what’s wrong. A better way is to take the initial fill amount at construction. The user is required to provide it and gets a prompt error if he doesn’t, so there’s no wondering later why the balloon didn’t make any noise when you prick it. RAII is a more specific case of “Don’t have invariants that aren’t.”
  • 82. Compelling examples A final way of providing grooves is through compelling examples. These are the most overt sort of grooves. The user knows he’s being advised. This screenshot from MacPaint 1.0, with its palettes on the left and menus on the top should look familiar to everybody in this room— even if they never used it—because Photoshop and Illustrator and the GIMP and Pixelmator and Paintshop Pro have been following its example for the past 30 years. [Another example: Nintendo platformers. Picture is in the margin.] Set a good example, and people will follow it forever. I used to write example code in a very pedagogical fashion, with outrageously long variable names and inefficiency everywhere. I ended up having to stop that, because users of my libs were copying and pasting my code for use in production! Oftentimes, they would even leave the comments! So don't underestimate the docility of your users; they will do what you suggest. This isn't because they're stupid. It's just that they're
  • 83. Compelling examples A final way of providing grooves is through compelling examples. These are the most overt sort of grooves. The user knows he’s being advised. This screenshot from MacPaint 1.0, with its palettes on the left and menus on the top should look familiar to everybody in this room— even if they never used it—because Photoshop and Illustrator and the GIMP and Pixelmator and Paintshop Pro have been following its example for the past 30 years. [Another example: Nintendo platformers. Picture is in the margin.] Set a good example, and people will follow it forever. I used to write example code in a very pedagogical fashion, with outrageously long variable names and inefficiency everywhere. I ended up having to stop that, because users of my libs were copying and pasting my code for use in production! Oftentimes, they would even leave the comments! So don't underestimate the docility of your users; they will do what you suggest. This isn't because they're stupid. It's just that they're
  • 84. Grooviness warning signs
  • 85. Grooviness warning signs Nonsense states or representations
  • 86. Grooviness warning signs Nonsense states or representations Invariants that aren’t
  • 87. Grooviness warning signs Nonsense states or representations Invariants that aren’t Users unclear where to get started
  • 88. Safety “I wish it was easier to hurt myself.” —Nobody, ever Now we get to the walls. Walls are to keep you from hurting yourself or others. Just as in the physical metaphor, there’s a continuum between grooves and walls. The more damage you can do with a feature, the higher the wall should be in front of it.
  • 89. Safety Groove Wall Now we get to the walls. Walls are to keep you from hurting yourself or others. Just as in the physical metaphor, there’s a continuum between grooves and walls. The more damage you can do with a feature, the higher the wall should be in front of it.
  • 90. update things set frob=2 where frob=1; Here is a horrible API with not enough walls. Does anyone see what’s wrong with it? Maybe it’s bitten you in the past. Who here has wiped out a whole DB column because they forgot the WHERE clause? One way to fix this might be to require you to say all if you mean all. Might be less mathematically beautiful, but imagine the hours it would have saved since the advent of SQL. Here’s another old chestnut. This is a pretty common thing to do. The trouble is—to do it, you first have to type this, which is seriously bad news. Don’t hit Return by accident! Maybe we could require -f for bare asterisks.
  • 91. update things set frob=2 where frob=1; update things set frob=2; Here is a horrible API with not enough walls. Does anyone see what’s wrong with it? Maybe it’s bitten you in the past. Who here has wiped out a whole DB column because they forgot the WHERE clause? One way to fix this might be to require you to say all if you mean all. Might be less mathematically beautiful, but imagine the hours it would have saved since the advent of SQL. Here’s another old chestnut. This is a pretty common thing to do. The trouble is—to do it, you first have to type this, which is seriously bad news. Don’t hit Return by accident! Maybe we could require -f for bare asterisks.
  • 92. update things set frob=2 where frob=1; update things set frob=2; update things set frob=2 all; Here is a horrible API with not enough walls. Does anyone see what’s wrong with it? Maybe it’s bitten you in the past. Who here has wiped out a whole DB column because they forgot the WHERE clause? One way to fix this might be to require you to say all if you mean all. Might be less mathematically beautiful, but imagine the hours it would have saved since the advent of SQL. Here’s another old chestnut. This is a pretty common thing to do. The trouble is—to do it, you first have to type this, which is seriously bad news. Don’t hit Return by accident! Maybe we could require -f for bare asterisks.
  • 93. update things set frob=2 where frob=1; update things set frob=2; update things set frob=2 all; rm *.pyc Here is a horrible API with not enough walls. Does anyone see what’s wrong with it? Maybe it’s bitten you in the past. Who here has wiped out a whole DB column because they forgot the WHERE clause? One way to fix this might be to require you to say all if you mean all. Might be less mathematically beautiful, but imagine the hours it would have saved since the advent of SQL. Here’s another old chestnut. This is a pretty common thing to do. The trouble is—to do it, you first have to type this, which is seriously bad news. Don’t hit Return by accident! Maybe we could require -f for bare asterisks.
  • 94. update things set frob=2 where frob=1; update things set frob=2; update things set frob=2 all; rm *.pyc rm * Here is a horrible API with not enough walls. Does anyone see what’s wrong with it? Maybe it’s bitten you in the past. Who here has wiped out a whole DB column because they forgot the WHERE clause? One way to fix this might be to require you to say all if you mean all. Might be less mathematically beautiful, but imagine the hours it would have saved since the advent of SQL. Here’s another old chestnut. This is a pretty common thing to do. The trouble is—to do it, you first have to type this, which is seriously bad news. Don’t hit Return by accident! Maybe we could require -f for bare asterisks.
  • 95. update things set frob=2 where frob=1; update things set frob=2; update things set frob=2 all; rm *.pyc rm * rm -f * Here is a horrible API with not enough walls. Does anyone see what’s wrong with it? Maybe it’s bitten you in the past. Who here has wiped out a whole DB column because they forgot the WHERE clause? One way to fix this might be to require you to say all if you mean all. Might be less mathematically beautiful, but imagine the hours it would have saved since the advent of SQL. Here’s another old chestnut. This is a pretty common thing to do. The trouble is—to do it, you first have to type this, which is seriously bad news. Don’t hit Return by accident! Maybe we could require -f for bare asterisks.
  • 96. def delete(self, index, doc_type, id=None): """Delete a typed JSON document from a specific index based on its id.""" A very similar thing happened in pyelasticsearch. This is what the delete method in pyelasticsearch used to look like. It deletes a document based on its ID. (A document is like a row in a RDB.) You might not be able to pick out what’s wrong unless you know ES, but if you make a DELETE API call to it without an ID, it’ll happily delete all documents. And id here is OPTIONAL! I can imagine myself coding along and managing to close your parentheses early. Or I could leave out one of the previous arguments so the value I intended as the ID gets interpreted as one of the other arguments instead. So we fixed that. Here’s how it looks now. We made ID mandatory and, to support the rare delete-all- documents case, we added a second routine explicitly called delete_all.
  • 97. def delete(self, index, doc_type, id=None): """Delete a typed JSON document from a specific index based on its id.""" A very similar thing happened in pyelasticsearch. This is what the delete method in pyelasticsearch used to look like. It deletes a document based on its ID. (A document is like a row in a RDB.) You might not be able to pick out what’s wrong unless you know ES, but if you make a DELETE API call to it without an ID, it’ll happily delete all documents. And id here is OPTIONAL! I can imagine myself coding along and managing to close your parentheses early. Or I could leave out one of the previous arguments so the value I intended as the ID gets interpreted as one of the other arguments instead. So we fixed that. Here’s how it looks now. We made ID mandatory and, to support the rare delete-all- documents case, we added a second routine explicitly called delete_all.
  • 98. def delete(self, index, doc_type, id=None): """Delete a typed JSON document from a specific index based on its id.""" def delete(self, index, doc_type, id): """Delete a typed JSON document from a specific index based on its id.""" def delete_all(self, index, doc_type): """Delete all documents of the given doctype from an index.""" A very similar thing happened in pyelasticsearch. This is what the delete method in pyelasticsearch used to look like. It deletes a document based on its ID. (A document is like a row in a RDB.) You might not be able to pick out what’s wrong unless you know ES, but if you make a DELETE API call to it without an ID, it’ll happily delete all documents. And id here is OPTIONAL! I can imagine myself coding along and managing to close your parentheses early. Or I could leave out one of the previous arguments so the value I intended as the ID gets interpreted as one of the other arguments instead. So we fixed that. Here’s how it looks now. We made ID mandatory and, to support the rare delete-all- documents case, we added a second routine explicitly called delete_all.
  • 99. Error reporting: exceptions are typically safer. The other thing to consider is how to report errors. You have 2 choices in most languages: return something, or throw an exception. Raising an exception is typically the safer of the two, as it reduces the chance of accidentally swallowing an error.
  • 100. Safetywarning signs In general, Ask yourself, "How can I use this to hurt myself and others?" People will blame themselves.
  • 101. Safetywarning signs Surprisingly few—people will blame themselves. In general, Ask yourself, "How can I use this to hurt myself and others?" People will blame themselves.
  • 102. non-astronautics consistency brevity composability plain data safety grooviness While you’re getting your questions together, I want to ask you one. I feel like… Elegance? If you have any ideas, please do share.
  • 103. non-astronautics consistency Elegance? brevity composability “It is the ineffable will of Bob.” —Old Thrashbarg plain data safety grooviness While you’re getting your questions together, I want to ask you one. I feel like… Elegance? If you have any ideas, please do share.
  • 104. Thank You Merci https://joind.in/7995 twitter: ErikRose erik@mozilla.com