fluid, fluent apis

104
Fluent, Fluid APIs “A poet is, before anything else, a person who is passionately in love with language.” W. H. Auden by Erik Rose APIs 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.

Upload: erik-rose

Post on 01-Jul-2015

1.134 views

Category:

Technology


2 download

DESCRIPTION

Programming is hard, but we can magnify our efforts with excellent API design. Let’s explore how, as we consider compactness, orthogonality, consistency, safety, coupling, state handling, layering, and more, illustrated with practical examples (and gruesome mistakes!) from several popular Python libraries.

TRANSCRIPT

Page 1: Fluid, Fluent APIs

Fluent,Fluid APIs

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

—W. H. Auden

by Erik RoseAPIs

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.

[I’ve extracted some principles here that we can put names to, but each leads into the other, such that I suspect they’re all aspects of the same thing—that ineffable quality we call elegance.] Perhaps it’s my lot to feel inarticulate describing it. Is it an “I’ll know it when I see it” thing?

It doesn’t matter what kind: spoken, visual, formal. I love symbolism and syntax in the abstract, but I have an engineer’s mind as well. I like to make things, and I bet you do too.

Page 2: Fluid, Fluent APIs

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

—W. H. Auden

by Erik RoseAPIsPoetic

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.

[I’ve extracted some principles here that we can put names to, but each leads into the other, such that I suspect they’re all aspects of the same thing—that ineffable quality we call elegance.] Perhaps it’s my lot to feel inarticulate describing it. Is it an “I’ll know it when I see it” thing?

It doesn’t matter what kind: spoken, visual, formal. I love symbolism and syntax in the abstract, but I have an engineer’s mind as well. I like to make things, and I bet you do too.

Page 3: Fluid, Fluent APIs

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

—W. H. Auden

by Erik Roseprogrammer^

APIsPoetic

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.

[I’ve extracted some principles here that we can put names to, but each leads into the other, such that I suspect they’re all aspects of the same thing—that ineffable quality we call elegance.] Perhaps it’s my lot to feel inarticulate describing it. Is it an “I’ll know it when I see it” thing?

It doesn’t matter what kind: spoken, visual, formal. I love symbolism and syntax in the abstract, but I have an engineer’s mind as well. I like to make things, and I bet you do too.

Page 4: Fluid, Fluent APIs

Ildefansodeafhearing parents, siblings, stupid?

Susan Schallercoweringmimicimaginery studentpointing“Oh! Everything has a name!”

Page 5: Fluid, Fluent APIs

“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.

Page 6: Fluid, Fluent APIs

“"e window became a different thing by having a symbol a$ached to it.”

He had a category for it. He could talk about a window to someone.

Page 7: Fluid, Fluent APIs

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”

—Martin Fowler

Inventing Language

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 youfail: frustrating all those who come after you

Example: urllib2 vs. Requests

Page 8: Fluid, Fluent APIs

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…

Page 9: Fluid, Fluent APIs

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…

Page 10: Fluid, Fluent APIs

Don’t Be An Architecture

AstronautMy 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.

Page 11: Fluid, Fluent APIs

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.

Page 12: Fluid, Fluent APIs

"e 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.

Page 13: Fluid, Fluent APIs

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.

Page 14: Fluid, Fluent APIs

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.

Page 15: Fluid, Fluent APIs

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.

Page 16: Fluid, Fluent APIs

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…

Page 17: Fluid, Fluent APIs

TasksPrint some text with forma$ing.

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…

Page 18: Fluid, Fluent APIs

TasksPrint some text with forma$ing.

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…

Page 19: Fluid, Fluent APIs

Language ConstructsFunctions, arguments, keyword arguments

DecoratorsContext 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.

Page 20: Fluid, Fluent APIs

Pa!erns, Protocols, Interfaces, and ConventionsSequencesIterators

Mappings

å

Language ConstructsFunctions, arguments, keyword arguments

DecoratorsContext 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.

Page 21: Fluid, Fluent APIs

“"ink like a wise man, but communicatein the language of the people.”

—William Butler Yeats

Consistency

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 pageput the log-in link in the upper rightand so on

When writing an API, realize:users will spend 90% time using other APIsYou 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.

2. Learning speed & retention.

Page 22: Fluid, Fluent APIs

Brilliant thing about early Mac project: explicit HIGsEvery 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.

Page 23: Fluid, Fluent APIs

get(key, default) > fetch(default, key)

Brilliant thing about early Mac project: explicit HIGsEvery 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.

Page 24: Fluid, Fluent APIs

TasksPrint some text at a location, then snap back.

Print some text with forma$ing.

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.

Page 25: Fluid, Fluent APIs

TasksPrint some text at a location, then snap back.

Print some text with forma$ing.

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.

Page 26: Fluid, Fluent APIs

TasksPrint some text at a location, then snap back.

Print some text with forma$ing.

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 callerConsistent: use with Python’s built-in templating language: less to learn.

mash-it-all-together methods...bold_yellow_on_black(...)

I kept this example in the Consistency section even though it tries something novel, because it’s good to see that all these things are about tradeoffs. If we could just turn all the knobs to 11, this would be easy, and we’d have a lot more good APIs.

Page 27: Fluid, Fluent APIs

TasksPrint some text at a location, then snap back.

Print some text with forma$ing.

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 callerConsistent: use with Python’s built-in templating language: less to learn.

mash-it-all-together methods...bold_yellow_on_black(...)

I kept this example in the Consistency section even though it tries something novel, because it’s good to see that all these things are about tradeoffs. If we could just turn all the knobs to 11, this would be easy, and we’d have a lot more good APIs.

Page 28: Fluid, Fluent APIs

TasksPrint some text at a location, then snap back.

Print some text with forma$ing.

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 callerConsistent: use with Python’s built-in templating language: less to learn.

mash-it-all-together methods...bold_yellow_on_black(...)

I kept this example in the Consistency section even though it tries something novel, because it’s good to see that all these things are about tradeoffs. If we could just turn all the knobs to 11, this would be easy, and we’d have a lot more good APIs.

Page 29: Fluid, Fluent APIs

from blessings import Terminal

t = Terminal()

print t.red_bold + 'Hello world' + t.normalprint t.red_on_white + 'Hello world' + t.normalprint 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.

Page 30: Fluid, Fluent APIs

Consistencywarning 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 pyelasticsearchFeeling like you’re inventing novel syntax. Use judgment: designs paying off rather than showing off.

Page 31: Fluid, Fluent APIs

Frequent references to your docs or source

Consistencywarning 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 pyelasticsearchFeeling like you’re inventing novel syntax. Use judgment: designs paying off rather than showing off.

Page 32: Fluid, Fluent APIs

Frequent references to your docs or sourceFeeling syntactically clever

Consistencywarning 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 pyelasticsearchFeeling like you’re inventing novel syntax. Use judgment: designs paying off rather than showing off.

Page 33: Fluid, Fluent APIs

“"e %nest language is mostly made upof simple, unimposing words.”

—George Eliot

Brevity

3rd rule of thumb is Brevity.

Make common things…short (in tokens, sometimes even in terms of chars if it doesn’t hurt understanding)

Page 34: Fluid, Fluent APIs

from blessings import Terminalterm = Terminal()

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

excerpt from earliernow 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.

Page 35: Fluid, Fluent APIs

from blessings import Terminalterm = Terminal()

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

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

excerpt from earliernow 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.

Page 36: Fluid, Fluent APIs

from blessings import Terminalterm = Terminal()

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

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

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

excerpt from earliernow 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.

Page 37: Fluid, Fluent APIs

Brevitywarning signs

Page 38: Fluid, Fluent APIs

Brevitywarning signs

Copying and pasting when writing against your API

Page 39: Fluid, Fluent APIs

Brevitywarning signs

Copying and pasting when writing against your APITyping something irrelevant while grumbling“Why can’t it just assume the obvious thing?”

Page 40: Fluid, Fluent APIs

“Perfection is achieved not when there is nothing le& to add but when there is nothing le& to take away.”

—Antoine de Saint-Exupery

Composability

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.

Page 41: Fluid, Fluent APIs

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.

Because we followed the Composability rule of thumb in Blessings, Jeff was able to stick formatted strings in memory really easily and pass them to his child processes. So although Jeff started x84 10 years ago and had already made a lot of architectural decisions, Blessings was an easy retrofit because it minimized the assumptions it made.

[Incidentally, Conway was contrived as an attempt at getting a second app, but it didn’t really stretch us enough. It was like formulating your hypothesis after the experiment is done. It’s post-hoc. It’s cheating. It was better to have another person introduce a new problem.]

Page 42: Fluid, Fluent APIs

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.

Because we followed the Composability rule of thumb in Blessings, Jeff was able to stick formatted strings in memory really easily and pass them to his child processes. So although Jeff started x84 10 years ago and had already made a lot of architectural decisions, Blessings was an easy retrofit because it minimized the assumptions it made.

[Incidentally, Conway was contrived as an attempt at getting a second app, but it didn’t really stretch us enough. It was like formulating your hypothesis after the experiment is done. It’s post-hoc. It’s cheating. It was better to have another person introduce a new problem.]

Page 43: Fluid, Fluent APIs

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.

Because we followed the Composability rule of thumb in Blessings, Jeff was able to stick formatted strings in memory really easily and pass them to his child processes. So although Jeff started x84 10 years ago and had already made a lot of architectural decisions, Blessings was an easy retrofit because it minimized the assumptions it made.

[Incidentally, Conway was contrived as an attempt at getting a second app, but it didn’t really stretch us enough. It was like formulating your hypothesis after the experiment is done. It’s post-hoc. It’s cheating. It was better to have another person introduce a new problem.]

Page 44: Fluid, Fluent APIs

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.

Because we followed the Composability rule of thumb in Blessings, Jeff was able to stick formatted strings in memory really easily and pass them to his child processes. So although Jeff started x84 10 years ago and had already made a lot of architectural decisions, Blessings was an easy retrofit because it minimized the assumptions it made.

[Incidentally, Conway was contrived as an attempt at getting a second app, but it didn’t really stretch us enough. It was like formulating your hypothesis after the experiment is done. It’s post-hoc. It’s cheating. It was better to have another person introduce a new problem.]

Page 45: Fluid, Fluent APIs

Composabiltywarning 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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 46: Fluid, Fluent APIs

Classes with lots of state

Composabiltywarning 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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 47: Fluid, Fluent APIs

Classes with lots of state

Composabiltywarning signs

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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 48: Fluid, Fluent APIs

Classes with lots of state

Composabiltywarning signs

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."""

. . .

class PenaltyBox(object): """A thread-safe bucket of servers (or other things) that may have downtime."""

def get(self): """Return a random server and a bool indicating whether it was from the dead list."""

def mark_dead(self, server): """Guarantee that this server won't be returned again until a period of time has passed, unless all servers are dead."""

def mark_live(self, server): """Move a server from the dead list to the live one."""

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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 49: Fluid, Fluent APIs

Classes with lots of stateDeep inheritance hierarchies

Composabiltywarning 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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 50: Fluid, Fluent APIs

Classes with lots of stateDeep inheritance hierarchies

Violations of the Law of Demeter

Composabiltywarning 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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 51: Fluid, Fluent APIs

Classes with lots of stateDeep inheritance hierarchies

Violations of the Law of DemeterMocking in tests

Composabiltywarning 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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 52: Fluid, Fluent APIs

Classes with lots of stateDeep inheritance hierarchies

Violations of the Law of DemeterMocking in tests

Composabiltywarning signs

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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 53: Fluid, Fluent APIs

Classes with lots of stateDeep inheritance hierarchies

Violations of the Law of DemeterMocking in tests

Composabiltywarning signs

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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 54: Fluid, Fluent APIs

Classes with lots of stateDeep inheritance hierarchies

Violations of the Law of DemeterMocking in tests

Options

Composabiltywarning signs

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.

This ran amuck in Zope 2, where we had such inheritance that we had to start inventing tooling to keep track of it for us. It was basically impossible to figure out where anything was happening.

3. Law of Demeter. Basically says “If you do compose, no cheating”. If ElasticSearch contains a PenaltyBox, ES can call methods on PB, but no reaching inside PB and examining its downed-nodes list directly. Public methods only! If you violate that, then there was no point in separating them in the first place.

4. Mocking. If you find yourself mocking out dependencies of your code, your code has too many dependencies. For example, how would you test this? You’d have to reach into sys and mock out stdout. This, OTOH, doesn’t require any mocking: just compare the returned string. Testable code is decoupled code.

5. Options

[If you need expansion, add 5. Going too far. (Zope 3—you can’t reason statically about your program, even a little.)]

Page 55: Fluid, Fluent APIs

“All the great things are simple, and many can be expressed in a single word…”

—Sir Winston Churchill

Plain Data

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?

Page 56: Fluid, Fluent APIs

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.

A method should TAKE WHAT IT NEEDS: no more. You need the string. You don’t need a file for any particular reason.

helper function

Then, if you must, add a convenience function which does both parts.

Page 57: Fluid, Fluent APIs

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.

A method should TAKE WHAT IT NEEDS: no more. You need the string. You don’t need a file for any particular reason.

helper function

Then, if you must, add a convenience function which does both parts.

Page 58: Fluid, Fluent APIs

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.

A method should TAKE WHAT IT NEEDS: no more. You need the string. You don’t need a file for any particular reason.

helper function

Then, if you must, add a convenience function which does both parts.

Page 59: Fluid, Fluent APIs

✚✚

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.

A method should TAKE WHAT IT NEEDS: no more. You need the string. You don’t need a file for any particular reason.

helper function

Then, if you must, add a convenience function which does both parts.

Page 60: Fluid, Fluent APIs

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.

A method should TAKE WHAT IT NEEDS: no more. You need the string. You don’t need a file for any particular reason.

helper function

Then, if you must, add a convenience function which does both parts.

Page 61: Fluid, Fluent APIs

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.

A method should TAKE WHAT IT NEEDS: no more. You need the string. You don’t need a file for any particular reason.

helper function

Then, if you must, add a convenience function which does both parts.

Page 62: Fluid, Fluent APIs

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.When you return a data structure or accept one, it can look like this (big object) or like this (dict or something). TODO: a compelling example

Lower barriersCommon tongue of communication between external interfaces. Nobody’s going to standardize on your representation unless you bring major, major advantages. So unless you have something really brilliant, keep it simple.Saves writing a bunch of code, because the language and stdlib already knows how to deal with stuff

Page 63: Fluid, Fluent APIs

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.When you return a data structure or accept one, it can look like this (big object) or like this (dict or something). TODO: a compelling example

Lower barriersCommon tongue of communication between external interfaces. Nobody’s going to standardize on your representation unless you bring major, major advantages. So unless you have something really brilliant, keep it simple.Saves writing a bunch of code, because the language and stdlib already knows how to deal with stuff

Page 64: Fluid, Fluent APIs

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.When you return a data structure or accept one, it can look like this (big object) or like this (dict or something). TODO: a compelling example

Lower barriersCommon tongue of communication between external interfaces. Nobody’s going to standardize on your representation unless you bring major, major advantages. So unless you have something really brilliant, keep it simple.Saves writing a bunch of code, because the language and stdlib already knows how to deal with stuff

Page 65: Fluid, Fluent APIs

Plain Datawarning signs

(exception: inversion of control. Data, not control.)

Page 66: Fluid, Fluent APIs

Plain Datawarning signs

Users immediately transforming your output to another format

(exception: inversion of control. Data, not control.)

Page 67: Fluid, Fluent APIs

Plain Datawarning signs

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

(exception: inversion of control. Data, not control.)

Page 68: Fluid, Fluent APIs

“"e bad teacher’s words fall on his pupils like harsh rain; the good teacher’s, as gently as dew.”

—Talmud: Ta’anith 7b

Grooviness

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.

Page 69: Fluid, Fluent APIs

GroovinessWallGroove

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.

Page 70: Fluid, Fluent 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.

Page 71: Fluid, Fluent APIs

{ "bool" : { "must" : { "term" : { "user" : "fred" } }, "must_not" : { "range" : { "age" : { "from" : 12, "to" : 21 } } }, "minimum_number_should_match" : 1, "boost" : 1.0 }}

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.

Page 72: Fluid, Fluent APIs

{ "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.

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.

Page 73: Fluid, Fluent APIs

def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results."""

Old pyelasticsearch

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.

Page 74: Fluid, Fluent APIs

def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results."""

Old pyelasticsearch

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.

Page 75: Fluid, Fluent APIs

def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results."""

Old pyelasticsearch

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.

Page 76: Fluid, Fluent APIs

def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results."""

Old pyelasticsearch

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.

Page 77: Fluid, Fluent APIs

def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results."""

Old pyelasticsearch

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

New pyelasticsearch

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.

Page 78: Fluid, Fluent APIs

def search(self, q=None, body=None, indexes=None, doc_types=None): """Execute an elasticsearch query, and return the results."""

Old pyelasticsearch

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

New pyelasticsearch

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.

Page 79: Fluid, Fluent APIs

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.”

Page 80: Fluid, Fluent APIs

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.”

Page 81: Fluid, Fluent APIs

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.”

Page 82: Fluid, Fluent APIs

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 not interested in re-solving the problem you solved. If they were, they wouldn't be using your library. They just want to get in, get their functionality, and get back to the interesting problem they're solving.

[Bools -> enum if you need more]

Page 83: Fluid, Fluent APIs

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 not interested in re-solving the problem you solved. If they were, they wouldn't be using your library. They just want to get in, get their functionality, and get back to the interesting problem they're solving.

[Bools -> enum if you need more]

Page 84: Fluid, Fluent APIs

Groovinesswarning signs

Page 85: Fluid, Fluent APIs

Groovinesswarning signs

Nonsense states or representations

Page 86: Fluid, Fluent APIs

Groovinesswarning signs

Nonsense states or representationsInvariants that aren’t

Page 87: Fluid, Fluent APIs

Groovinesswarning signs

Nonsense states or representationsInvariants that aren’t

Users unclear where to get started

Page 88: Fluid, Fluent APIs

“I wish it was easier to hurt myself.”—Nobody, ever

Safety

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.

Page 89: Fluid, Fluent APIs

SafetyWallGroove

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.

Page 90: Fluid, Fluent APIs

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.

Page 91: Fluid, Fluent APIs

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.

Page 92: Fluid, Fluent APIs

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.

Page 93: Fluid, Fluent APIs

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.

Page 94: Fluid, Fluent APIs

update things set frob=2 where frob=1;

update things set frob=2;

update things set frob=2 all;

rm *.pycrm *

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.

Page 95: Fluid, Fluent APIs

update things set frob=2 where frob=1;

update things set frob=2;

update things set frob=2 all;

rm *.pycrm *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.

Page 96: Fluid, Fluent APIs

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.

Page 97: Fluid, Fluent APIs

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.

Page 98: Fluid, Fluent APIs

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.

Page 99: Fluid, Fluent APIs

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.

Page 100: Fluid, Fluent APIs

Safetywarning signs

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

People will blame themselves.

Page 101: Fluid, Fluent APIs

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.

Page 102: Fluid, Fluent APIs

non-astronautics consistency

brevity composability

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

Elegance?

If you have any ideas, please do share.

Page 103: Fluid, Fluent APIs

“It is the ineffable will of Bob.”—Old "rashbarg

Elegance?non-astronautics consistency

brevity composability

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

Elegance?

If you have any ideas, please do share.

Page 104: Fluid, Fluent APIs

h$ps://joind.in/7995twi$er: [email protected]

!ank YouMerci