 Good morning. Thank you. I Learned blender just to have this cool effect. It's interest light effect. I'm kind of in distance wave lately Anyways, good morning. My name is Len Root as Martin said I am a staff engineer at Spotify and I am based in New York I've been building infrastructure for audio processing for the past few years And you also might know me from Pyle ladies. I'm the global chair of the Pyle ladies global council So, yes, it has been a few years. I've completely forgotten how to write like a succinct Concise talk, so I'm gonna be using my full time and we'll be taking questions like out in the hall during coffee So now I'm just gonna jump right into it about 10 years ago I bought an electric kettle from Breville and it had this plug And essentially this plug like blew my mind. I know it's an American plug, but I found myself Why don't all plugs have a hole like this? The answer is probably because of patents But it not only doesn't make it really easy to unplug from a socket You don't have to you know wiggle back and forth and maybe electrocute yourself But it's very obvious how to use it. It allows for easy grabbing and polling And so the simplicity the thoughtfulness of this design. This has made me this made me think what else am I unknowingly struggling with So this brought me to this book called the design of everyday things by Don Norman, which you might notice is the inspiration for my talks title It's got this nice coffee pot on the cover. It's dubbed the coffee pot for masochists And the premise of this book is about how design serves as Communication between object and user and how to optimize that communication for a better experience So Norman writes Design is concerned with how things work how they are controlled and the nature of the interaction between people and technology The range of things is not limited to physical objects like the Breville plug that I'm in love with or the masochistic Tea pot or coffee pot on the books cover It includes any artificial creation like software or digital interfaces the layout of a conference room manual for an appliance Organizational structures, etc. So what makes design good? Norman says two of the most important characteristics of good design or discoverability and understanding So if we break this down further discoverability is Described as am I able to figure out what actions are possible and where and how to perform them? the author lays out five key elements to discoverability and I'm sorry. I'm gonna breathe a breeze right through them for the sake of time But the first one is affordances affordances determine what action What actions are possible and it's what a user can do with an object based on the user's capabilities? Building on top of affordances a second element is signifiers signifier is some sort of perceivable cue about the affordance The third key element is discoverability of discoverability is constraints constraints are limitations or restrictions And can give us clues that allow us to determine a course of action by limiting the possible actions available to us The fourth one is mappings a mapping is a relationship between a control and an action An example of a good mapping is you might have two lights and two light switches and the left light switch controls the left light and vice versa The final key element is feedback where results of an action is communicated back to the user And so immediate feedback is ideal delayed feedback can be disconcerting and lead to a feeling of user abandonment and failure And so these key elements of discoverability help build up the second part of good design, which is understanding So Understandability means is it possible or easy for users to figure out how the product can be used Understanding is developed by forming a conceptual or mental model of how something works A conceptual model is just a collection of explanations often oversimplified And the user has their own conceptual model of how a system or a product works and the designer Designs their system or product using their own conceptual model And then designers expect users conceptual model to be identical to their own But because they cannot communicate directly with the user the burden of communication is with the system and its design So good conceptual models are the key to understandable enjoyable products And good communication is the key to good conceptual models And then that communication comes from those key aspects of discovery Affordances signifiers constraints mapping and feedback And so maybe you're able to start seeing how these ideas essentially humid-centered design can start to apply to software To designing a library for others to use My first instinct when thinking about libraries I've I've worked with that maybe They're not fun to use. I didn't really want to name and shame any Python libraries here But I'm gonna call it one non Python library that I use that I do loathe is ffmpeg You're not supposed to be able to read this screen But ffmpeg it's a well-known CLI tool that's designed for processing audio and video This is a screen recording of me just running a command to list some documentation Whenever I use ffmpeg it's a way for me To figure out how to do something like seemingly simple. I have to like actually look up a stack overflow Because I'm sorry the tools documentation really sucks and I know that I will continue to reach for it because of its ubiquity and its performance, but there will always be this sort of Kind of sigh reaction to it And so this made me think if I am an engineer who develops tools and infrastructure for other engineers What kind of masochistic teapots have I unknowingly shipped? How many size have I caused? So there are much much better people like to give talks on on this idea With much better ideas and theories, so I'm not an authority on the API design But for me there's this missing connection between the theory of good API design and the actual implementation So this brings me to why I'm here today I'm not sure if folks have seen previous talks of mine But I have a habit of writing talks for myself ones that I really wish existed a few years ago So this talk is mainly for past Lynn a talk that tries to take Elements key elements of good API design and apply them to a real-world example And so I was able to condense this into three tenants three key principles that make a delightful to work with API So first I'm going to set the stage. So let's say that we are designing a library to work with We're worth a pub sub queue And we're gonna just call it chaos queue because why not and it has nothing to do with this car. I just like like it So let's start with some code First we have a class that defines the message object And it all it does is contain data to be passed to and from a chaos queue And We have a second class a client that essentially publishes to and consumes messages from our chaos pub sub queue And this is our starting library and perhaps you're already thinking of some things that you want to change Which is good, but like hang tight for a while In order to develop some empathy for our future users We want to write a little bit of user code that interacts with our library So on the user side and we create a chaos pub sub queue client and then create a topic and subscription catching and moving on if they already exist and Then we start interacting with our client We first have a for loop to publish five messages to our chaos pub sub queue and Then a while true loop to consume those messages and then we print the message maybe leftover debugging And then we do some sort of processing of that message data, and then we acknowledge the message from the queue when we're done processing And then finally maybe the client has a couple of network connections that we need to clean up and close Maybe some buffers we need to flush So let's improve this for the user and let's start iterating on our library We'll start with my first of three principles An API should be intuitive With an intuitive API a user can be lazy They don't have to think too hard about how it works. They won't have to remember complicated details Because their intuition correctly explains them They won't have to spend a lot of time learning new parts of the API because it just all works similarly So an intuitive API builds on the user's pre-existing conceptual model and it tries to not do anything surprising Nothing that unnecessarily works against any logical or domain constraints And so with that I have three changes that I want to make to our chaos queue to improve the intuitiveness of the API We'll start with some low-hanging fruit And name the methods in our client class using domains nomenclature like pulling a message Publishing a message Acknowledging a message is complete and draining a queue of messages So right now we have add message get message mark message done and clear message queue Let's just rename them to publish and pull act and drain I've also dropped the noun from the method names since I found that a little bit redundant Particularly because in my mind the noun that we're operating on is in the function signature at the message and Now I have room on my slide to indent for spaces And so why are we doing this this plays into the mappings element of discoverability Naming your functions similar something familiar to the domain that they're supposed to work in can help Result an immediate understanding of what your API is supposed to do The next suggested change steps back just a tiny bit and to think about the appropriate abstraction levels clumsy naming hints at clumsy abstractions Even the slide is a little clumsy But if you recall the pub sub-client we have a few curious methods create topic create subscription and close client if this client I mean this client seems to be managing a lot with one object from the user's point of view I know what if we're just wanting to consume messages and not publish them Do we have to create a topic for some reason? Will there be multiple connections managed by this client some unnecessary? So let's not add any unnecessary confusion to the user and break this up into two One client that works with publishing and one that works for subscribing. It's also allows us to clean up the method names a little bit This then creates national Natural constraints on what the user can do if the user must create a topic They have to instantiate a pub client and are limited to only those actions provided within the pub clients methods However, these clients also create some unnecessary constraints on our users which brings me to my third change Let's make sure our API has symmetry So we allow the user to Create a topic and create a subscription But what does a user do if they need to change an existing topic or subscription or delete one? So let's not limit the user unnecessarily and provide symmetry with our methods Just having a create method without its logical symmetric pair or triplet and gives a mislead misleading signifier to the user So you can be certain that users have worked with other apis that provide both a create and delete method I get an asset method an upload and download method It wouldn't be counterintuitive to not provide such functionality or to provide that functionality But not follow the convention of symmetric naming So some of these changes they might seem a bit contrived. It's it's hard to come up with a simple succinct example That's also relatable, but hopefully the message is coming through and some of the changes that come later Could later in this talk could very well apply to the intuitive tenant And I'm sure the changes that I'm not addressing might even apply here or another circumstances again. I'm not the expert But let's move on to the next tenant an API should be flexible And I mean flexible for the user to hold another talk to write about flexible APIs for a maintainer or contributor But basically a flexible API lets you do what you want in my point of view a flexible API lets users Get started quickly with the more basic use cases and then allow users to adjust as they need to solve more complex problems Flexibility comes down to the question of how many problems can users solve once they learn the API So for this tenant I have four changes that I want to make to our chaos queue The first is to provide sane defaults to the most common use cases So recall that some of our methods that work with publishing and consuming messages The main resource these methods are working with is the message the timeout and retry arguments are not required to the core functionality of publishing polling or acknowledging And often I found myself as a user to not know what a good number of seconds timeout should be or how many retries I should be making So as an API designer I should make optional behavior as optional keyword arguments Maybe 30 seconds is the most common timeout value or maybe it's just the default from the server and maybe it's the default to have zero retries By moving a lot of optional settings to keyword arguments it allows users to focus on just what's required of them this change also Goes beyond the flexibility of an API By providing or by forcing the use of keyword arguments and providing sane defaults We limit the number and the order of positional arguments that a user has to remember So for instance both timeout and retries are integers if they were positional arguments like before the user could easily inverse them When calling the method and that would safe safely pass type checkers So maybe we have many more optional keyword arguments for our methods like request ID verbosity level maybe a bunch more If you have many many arguments positional or keyword You might want to step back a little bit because it might be another hint at clumsy code You might be unnecessarily exposing some complexity So with type hints and with a lot of arguments if folks can't understand your type annotations You might need to rethink and refactor So for the sake of my slides being readable, I'll be rolling these up into star quarks And so for the second change I want to make to increase flexibility is to minimize The user's need to repeat themselves. You might remember we saw a loop in the user's code to To publish that they were calling to publish each message And so in our pub client our publish message has just one positional argument for message To minimize forcing users to repeatedly call publish for each message And we can accept multiple message objects in the function signature by collecting them into star args and Then we can you know do the repetitive work for them So hopefully you're writing some user code as you're building your API Since while doing so you can look for common patterns that user in that user code that you can Minimize away things like loops and repetition As I said earlier the the tenant of flexibility is meant for the user's point of view and providing flexibility to the user requires you to be Predictable and to be precise in what you do for them If we recall part of building that conceptual model that understanding for the user is providing constraints and feedback We provide flexibility in what we accept as input, but there needs to be a clear constraint in what we return We shouldn't make the user figure out what type or what object type they got back from a method of ours And so if you recall our subscription client that returns a consumed message Or it returns none. It doesn't seem too precise or predictable to me So the user has to check if they got a message at all or if they are actually dealing with none So what's the none use case here? Well, it's when there are no more messages to consume If we take a look at pre-existing API's like the Python queue library It raises an error when a queue is empty and that seems pretty reasonable to me So let's update our function signature to only return the message object and then raise if there are no messages No more messages in this subscription queue And so this goes along with what Don Norman said about feedback that it should Communicate clear unambiguous information back to the user in order to be effective This last change for flexibility. I want to Increase the user's laziness I guess our ability to be lazy. Let's not force the users to provide data that you can generate yourself So recall this message object that has an ID some data in a published at time stand of some sort Now when we're creating a message, there's no need for users to supply their own unique ID We should probably be keeping track of that for them or to timestamp it when it was published and We can do that ourselves Now we probably would not have a timestamp be set at the time the message is instantiated It'd probably be set at when it's actually published But coming up with real-world examples is a bit difficult. So if you can just go along with me here So hopefully we have a bit of user understanding as well often users will want to print or log a message for debugging purposes And for that we should provide a wrapper method to make it easy for them to just print out the message message instance Now the user won't need to worry about printing the necessary attributes on the message, but just the message itself So this is just one approach to redefining our message object to help out the user We could also use data classes. This removes some boilerplate for us It does get a little clumsy to set defaults in our in our case with using post in it And the code goes a bit off to the side, but it's still nice a third option would be to use the a third-party package The author of adders is right here And so adders allows me to go back to a force-based indentation But it also allows me if needed to provide more flexibility and constraints for my users Since not only does it have a clean way for me to set dynamic defaults But it also has validation converters and even the ability to abuse in it more so if I needed I'm not a paid shill for adders. I'm just a happy user So if your API is not flexible if you find yourself saying, you know, I'm sorry I know our API should do that But for reasons that you don't care and that are within our control we can't if the API is not flexible Users often will ditch the library and go to somewhere else to solve their problems And so for my last tenant an API should be simple The complexity of an API can be measured by the cognitive load. It requires to actually use it complexity hurts our understanding We've already made some changes to reduce the cognitive load of our API like Consistent and appropriate method naming limiting the number of positional arguments in our function signatures And minimizing the amount of repetition the user has to do but we can make a few more changes The first change is to provide composable functions API API's that follow the mathematical closure property tend to be simple as well as flexible Loosely an API holds the closure property when every operation returns a data type that can be fed into another operation So this means that different operations of your API can be composed with each other For example in Python We're able to chain a bunch of string methods together since the output of many string methods is yet another string So this closure property makes it easy to combine operations to get the desired result maybe users don't often do this but Following this where possible allows users not to have to remember differing function signatures operating on the same object So if you look at our subscription client, we have two methods That operate on the message object. It's pretty reasonable to think that once you pull a message from the chaos queue You'll want to acknowledge it. Otherwise, it'll get redelivered and you get some duplicate work So there's no reason for us to be restrictive in our function signature for acting and requiring a string And that adds cognitive load to the user They have to remember that they must give an ID For of the message that they want to act and can't just pass in the object that they've been working with So let's change that to make it a message object and then we can figure out how to get the ID of the message from there for them and Now it's simpler for a user to just pull a message from a subscription queue and then act it and then while we're at it We can also accept that Create the ability to accept multiple messages if the user wants to batch up their acknowledgments And it certainly adds to the symmetry of our method signatures The next one we want to align our api with some Python idioms making it more pythonic Leveraging Python idioms also leans into what the programming language affords Different different programming languages allow you or afford you different approaches to the kinds of problems we tend to solve like supporting Reflection and introspection or supporting default values for function parameters or how to define an iterator Which leads me to what's looking at our next Subscription client and the user has to call pull to get a single message and as you might recall the user had a while true loop Make it so let's make it easier for the user and provide an iter method that returns an iterable So providing an iterator is helpful for cases like this where the user has is consuming a stream of events or data It's also helpful helpful for users might when they might need to like page through results like a search query with hundreds or thousands of items And so this iter method also happens to simplify users code and even more The iterable just stops when there are no more messages the user now can remove a try and accept for an empty queue And so in continuing on leveraging the pythons idioms You might remember that both clients have a closed method managing any connections and buffers underneath the hood This forces the user to remember to clean up after them after themselves adding to complexity and cognitive load So you might see where this is going Let's make use of a context manager providing an easy way for users to not have to worry about remembering any finalization or cleanup behavior So for the last change We can also think of simplicity as Convenient to get started. How much do I have to learn to start being able to use this library? What do I have to do right now to get this working if? The API is not convenient if it takes many steps to get started or if I have to page through docs in order to get a Very basic understanding then the tools adoption will suffer So provide your user with convenience and you do this by treating your read me like you would a newspaper Give them the most important details first like above the fold and if users want to learn more they can turn the page So when I'm looking at a read me, I want three things above the fold How do I get your library? Do I need any system level or non-python dependencies here? And then one what are one or two examples that I can copy and paste and to immediately try this out And the last one where do I go from more information? So just give me these three things at the top and I'm good to go to get started with your API So creating a simple API is all about reducing cognitive load on the user Reducing what the user has to keep in their head while working with your API And I know that was a lot. So let's rewind and review Real quick. So we started here with a very simple API that only contains two classes as we went on We broke our code into three classes. So this slide's a bit unreadable But that's okay because we started with a lot of user code 29 lines to be exact and we got it down to just 14 lines with our changes So now this code fits onto a one slide completely and is readable So this should be the goal not necessarily the number of lines of code But it's about user empathy when designing a delightful API So to recap is your API intuitive for the user? Is it flexible for their use cases? Is it simple enough to essentially learn once? So you have to keep in mind your software will become a part of a larger system Your only choice is whether it will be a well-behaved part of the larger system The how can be a bit elusive. So hopefully this example progression and improving this dummy API was helpful But it's a lot to remember. I know so if you can only remember one thing from this talk besides my awesome tiki synthwave theme Is this if all else fails? Standardize Don Norman leaves us with a good catch all when no other solution appears possible Then design everything the same way so people only have to learn once So you can find my slide and some additional resources here. Thank you very much Thank you Lynn for that very informative and inspiring talk We alas do not have times for question, but Lynn will be around on the conference So you can try to get her and ask your questions directly and you can also watch the recording of this talk on YouTube If you want to just replay with the examples because I think we all learned a lot today Thank you very much. Let's have another round of applause for Lynn