Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Let's make a contract: the art of designing a Java API

1 380 vues

Publié le

An API is what developers use to achieve some task. More precisely it establishes a contract between them and the designers of the software exposing its services through that API. In this sense we're all API designers: our software doesn't work in isolation, but becomes useful only when it interacts with other software written by other developers. When writing software we're not only consumers, but also providers of one or more API and that's why every developer should know the features of a good API. During this presentation we will go through real-world examples, also taken from the standard Java API, of good and bad API and comment them in order to show the dos and don'ts of API design. More in general we will discuss the characteristics of an easy and pleasant to use API, like consistency, discoverability and understandability, together with some basic concepts like the principle of least astonishment, and find out how to achieve them.

Publié dans : Logiciels
  • Soyez le premier à commenter

Let's make a contract: the art of designing a Java API

  1. 1. Let's make a contract: the art of designing a Java API by Mario Fusco mario.fusco@gmail.com @mariofusco
  2. 2. What is an API?
  3. 3. What is an API?
  4. 4. An API is what a developer uses to achieve some task What is an API?
  5. 5. What is an API? An API is a contract between its implementors and its users
  6. 6. And why should I care? We are all API designers Our software doesn't work in isolation, but becomes useful only when it interacts with other software written by other developers
  7. 7. Basic Principles ● Intuitive ● Understandable ● Learnable ● Discoverable ● Consistent ● Self-defensive ● Concise ● Easy to use ● Minimal ● Orthogonal ● Idiomatic ● Flexible ● Evolvable ● Well documented ● Right level of abstraction ● Correct use of the type system ● Limited number of entry-points ● Respect the principle of least astonishment
  8. 8. Be ready for changes
  9. 9. Be ready for changes Software being 'done' is like lawn being 'mowed' – Jim Benson Change is the only constant in software Add features sparingly and carefully so that they won’t become obstacles for the evolution of your API
  10. 10. If in doubt, leave it out A feature that only takes a few hours to be implemented can ➢ create hundreds of hours of support and maintenance in future ➢ bloat your software and confuse your users ➢ become a burden and prevent future improvements “it’s easy to build” is NOT a good enough reason to add it to your product
  11. 11. Best Practices & Practical Hints
  12. 12. Write meaningful Javadocs
  13. 13. Write meaningful Javadocs
  14. 14. Write meaningful Javadocs
  15. 15. Write meaningful Javadocs
  16. 16. Convenience methods Use overloading judiciously and sparingly
  17. 17. Convenience methods Use overloading judiciously and sparingly Primitives often cause methods proliferation {
  18. 18. Convenience methods Use overloading judiciously and sparingly Are these all necessary? Primitives often cause methods proliferation {
  19. 19. Convenience methods public interface StockOrder { void sell(String symbol, double price, int quantity); void buy(String symbol, int quantity, double price); void buy(String symbol, int quantity, double price, double commission); void buy(String symbol, int quantity, double minPrice, double maxPrice, double commission); } What’s wrong with this?
  20. 20. Convenience methods public interface StockOrder { void sell(String symbol, double price, int quantity); void buy(String symbol, int quantity, double price); void buy(String symbol, int quantity, double price, double commission); void buy(String symbol, int quantity, double minPrice, double maxPrice, double commission); } Too many overloads What’s wrong with this?
  21. 21. Convenience methods public interface StockOrder { void sell(String symbol, double price, int quantity); void buy(String symbol, int quantity, double price); void buy(String symbol, int quantity, double price, double commission); void buy(String symbol, int quantity, double minPrice, double maxPrice, double commission); } Too many overloads Inconsistent argument order What’s wrong with this?
  22. 22. Convenience methods Long arguments lists (especially of same type) public interface StockOrder { void sell(String symbol, double price, int quantity); void buy(String symbol, int quantity, double price); void buy(String symbol, int quantity, double price, double commission); void buy(String symbol, int quantity, double minPrice, double maxPrice, double commission); } Too many overloads Inconsistent argument order What’s wrong with this?
  23. 23. Convenience methods Long arguments lists (especially of same type) public interface StockOrder { void sell(String symbol, double price, int quantity); void buy(String symbol, int quantity, double price); void buy(String symbol, int quantity, double price, double commission); void buy(String symbol, int quantity, double minPrice, double maxPrice, double commission); } public interface StockOrder { void sell(String symbol, int quantity, Price price); void buy(String symbol, int quantity, Price price); } Too many overloads Inconsistent argument order What’s wrong with this? How to do better
  24. 24. Consider static factories public interface Price { static Price price( double price ) { if (price < 0) return Malformed.INSTANCE; return new Fixed(price); } static Price price( double minPrice, double maxPrice ) { if (minPrice > maxPrice) return Malformed.INSTANCE; return new Range(minPrice, maxPrice); } class Fixed implements Price { private final double price; private Fixed( double price ) { this.price = price; } } class Range implements Price { private final double minPrice; private final double maxPrice; private Range( double minPrice, double maxPrice ) { this.minPrice = minPrice; this.maxPrice = maxPrice; } } enum Malformed implements Price { INSTANCE } } ➢ nicer syntax for users (no need of new keyword)
  25. 25. Consider static factories public interface Price { static Price price( double price ) { if (price < 0) return Malformed.INSTANCE; return new Fixed(price); } static Price price( double minPrice, double maxPrice ) { if (minPrice > maxPrice) return Malformed.INSTANCE; return new Range(minPrice, maxPrice); } class Fixed implements Price { private final double price; private Fixed( double price ) { this.price = price; } } class Range implements Price { private final double minPrice; private final double maxPrice; private Range( double minPrice, double maxPrice ) { this.minPrice = minPrice; this.maxPrice = maxPrice; } } enum Malformed implements Price { INSTANCE } } ➢ nicer syntax for users (no need of new keyword) ➢ can return different subclasses
  26. 26. Consider static factories public interface Price { static Price price( double price ) { if (price < 0) return Malformed.INSTANCE; return new Fixed(price); } static Price price( double minPrice, double maxPrice ) { if (minPrice > maxPrice) return Malformed.INSTANCE; return new Range(minPrice, maxPrice); } class Fixed implements Price { private final double price; private Fixed( double price ) { this.price = price; } } class Range implements Price { private final double minPrice; private final double maxPrice; private Range( double minPrice, double maxPrice ) { this.minPrice = minPrice; this.maxPrice = maxPrice; } } enum Malformed implements Price { INSTANCE } } ➢ nicer syntax for users (no need of new keyword) ➢ can return different subclasses ➢ can check preconditions and edge cases returning different implementations accordingly
  27. 27. Promote fluent API public interface Price { Price withCommission(double commission); Price gross(); } public interface Price { void setCommission(double commission); void setGross(); }
  28. 28. Promote fluent API public interface Price { Price withCommission(double commission); Price gross(); } stockOrder.buy( "IBM", 100, price(150.0).withCommission(0.7).gross() ); public interface Price { void setCommission(double commission); void setGross(); } Price price = price(150.0); price.setCommission(0.7); price.setGross(); stockOrder.buy( "IBM", 100, price );
  29. 29. Promote fluent API public interface Price { Price withCommission(double commission); Price gross(); } stockOrder.buy( "IBM", 100, price(150.0).withCommission(0.7).gross() ); public interface Price { void setCommission(double commission); void setGross(); } Price price = price(150.0); price.setCommission(0.7); price.setGross(); stockOrder.buy( "IBM", 100, price ); Concatenate multiple invocations Use result directly
  30. 30. Promote fluent API Streams are a very nice and convenient example of fluent API
  31. 31. Promote fluent API Streams are a very nice and convenient example of fluent API
  32. 32. Promote fluent API Name consistency??? Streams are a very nice and convenient example of fluent API
  33. 33. Use the weakest possible type public String concatenate( ArrayList<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); }
  34. 34. Use the weakest possible type public String concatenate( ArrayList<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); } Do I care of the actual List implementation?
  35. 35. Use the weakest possible type public String concatenate( List<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); }
  36. 36. Use the weakest possible type public String concatenate( List<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); } Do I care of the elements’ order?
  37. 37. Use the weakest possible type public String concatenate( Collection<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); }
  38. 38. Use the weakest possible type public String concatenate( Collection<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); } Do I care of the Collection’s size?
  39. 39. Use the weakest possible type public String concatenate( Iterable<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); }
  40. 40. Using the weakest possible type... public String concatenate( Iterable<String> strings ) { StringBuilder sb = new StringBuilder(); for (String s : strings) { sb.append( s ); } return sb.toString(); } … enlarges the applicability of your method, avoiding to restrict your client to a particular implementation or forcing it to perform an unnecessary and potentially expensive copy operation if the input data exists in other forms
  41. 41. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { List<Address> addresses = new ArrayList<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; }
  42. 42. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { List<Address> addresses = new ArrayList<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; } Is the order of this List meaningful for client?
  43. 43. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { List<Address> addresses = new ArrayList<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; } Is the order of this List meaningful for client? … and shouldn’t we maybe return only the distinct addresses? Yeah, that will be easy let’s do this!
  44. 44. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { Set<Address> addresses = new HashSet<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; } It should be enough to change this List into a Set
  45. 45. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { Set<Address> addresses = new HashSet<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; } It should be enough to change this List into a Set But this doesn’t compile :(
  46. 46. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { Set<Address> addresses = new HashSet<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; } It should be enough to change this List into a Set But this doesn’t compile :( and I cannot change the returned type to avoid breaking backward compatibility :(((
  47. 47. Use the weakest possible type also for returned value public List<Address> getFamilyAddresses( Person person ) { Set<Address> addresses = new HashSet<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return new ArrayList<>( addresses ); } I’m obliged to uselessly create an expensive copy of data before returning them
  48. 48. Use the weakest possible type also for returned value public Collection<Address> getFamilyAddresses( Person person ) { List<Address> addresses = new ArrayList<>(); addresses.add(person.getAddress()); for (Person sibling : person.getSiblings()) { addresses.add(sibling.getAddress()); } return addresses; } Returning a more generic type (if this is acceptable for your client) provides better flexibility in future
  49. 49. Support lambdas public interface Listener { void beforeEvent(Event e); void afterEvent(Event e); } class EventProducer { public void registerListener(Listener listener) { // register listener } } public interface Listener { void beforeEvent(Event e); void afterEvent(Event e); } public interface Listener { void beforeEvent(Event e); void afterEvent(Event e); } EventProducer producer = new EventProducer(); producer.registerListener( new Listener() { @Override public void beforeEvent( Event e ) { // ignore } @Override public void afterEvent( Event e ) { System.out.println(e); } } );
  50. 50. Support lambdas class EventProducer { public void registerBefore(BeforeListener before) { // register listener } public void registerAfter(AfterListener after) { // register listener } } @FunctionalInterface interface BeforeListener { void beforeEvent( Event e ); } @FunctionalInterface interface AfterListener { void afterEvent( Event e ); } EventProducer producer = new EventProducer(); producer.registerAfter( System.out::println ); Taking functional interfaces as argument of your API enables clients to use lambdas
  51. 51. Support lambdas class EventProducer { public void registerBefore(Consumer<Event> before) { // register listener } public void registerAfter(Consumer<Event> after) { // register listener } } @FunctionalInterface interface BeforeListener { void beforeEvent( Event e ); } @FunctionalInterface interface AfterListener { void afterEvent( Event e ); } EventProducer producer = new EventProducer(); producer.registerAfter( System.out::println ); Taking functional interfaces as argument of your API enables clients to use lambdas In many cases you don’t need to define your own functional interfaces and use Java’s one
  52. 52. public void writeList( Writer writer, Collection<String> strings ) { strings.stream().forEach( writer::write ); } Writer writer = new StringWriter(); List<String> strings = asList("one", "two", "three"); writeList( writer, strings ); Avoid checked exceptions
  53. 53. Avoid checked exceptions public void writeList( Writer writer, Collection<String> strings ) { strings.stream().forEach( writer::write ); } Writer writer = new StringWriter(); List<String> strings = asList("one", "two", "three"); writeList( writer, strings ); public void writeList( Writer writer, Collection<String> strings ) { strings.stream().forEach( str -> { try { writer.write( str ); } catch (IOException e) { throw new RuntimeException( e ); } } ); }
  54. 54. Avoid checked exceptions Useful error management or Wasteful bloatware ?
  55. 55. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { FileInputStream file = new FileInputStream( filename ); byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); }
  56. 56. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { FileInputStream file = new FileInputStream( filename ); byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); } File descriptor leak
  57. 57. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { FileInputStream file = new FileInputStream( filename ); try { byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); } finally { file.close(); } }
  58. 58. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { FileInputStream file = new FileInputStream( filename ); try { byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); } finally { file.close(); } } We can do better using try-with-resource
  59. 59. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { try ( FileInputStream file = new FileInputStream( filename ) ) { byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); } }
  60. 60. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { try ( FileInputStream file = new FileInputStream( filename ) ) { byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); } } Better, but we’re still transferring to our users the burden to use our API correctly
  61. 61. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { try ( FileInputStream file = new FileInputStream( filename ) ) { byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); } } Better, but we’re still transferring to our users the burden to use our API correctly That’s a leaky abstraction!
  62. 62. Stay in control (loan pattern) public static <T> T withFile( String filename, ThrowingFunction<FileInputStream, T> consumer ) throws IOException { try ( FileInputStream file = new FileInputStream( filename ) ) { return consumer.apply( file ); } } @FunctionalInterface public interface ThrowingFunction<T, R> { R apply(T t) throws IOException; } Yeah, checked exceptions suck :(
  63. 63. Stay in control (loan pattern) public byte[] readFile(String filename) throws IOException { return withFile( filename, file -> { byte[] buffer = new byte[4096]; ByteArrayOutputStream out = new ByteArrayOutputStream( buffer.length ); int n = 0; while ( (n = file.read( buffer )) > 0 ) { out.write( buffer, 0, n ); } return out.toByteArray(); }); } Now the responsibility of avoiding the leak is encapsulated in our API If clients are forced to use this API no leak is possible at all!
  64. 64. Break apart large interfaces into smaller versions public interface RuleEngineServices { Resource newUrlResource( URL url ); Resource newByteArrayResource( byte[] bytes ); Resource newFileSystemResource( File file ); Resource newInputStreamResource( InputStream stream ); Resource newClassPathResource( String path ); void addModule( Module kModule ); Module getModule( ReleaseId releaseId ); Module removeModule( ReleaseId releaseId ); Command newInsert( Object object ); Command newModify( FactHandle factHandle ); Command newDelete( FactHandle factHandle ); Command newFireAllRules( int max ); RuntimeLogger newFileLogger( RuntimeEventManager session, String fileName, int maxEventsInMemory ); RuntimeLogger newThreadedFileLogger( RuntimeEventManager session, String fileName, int interval ); RuntimeLogger newConsoleLogger( RuntimeEventManager session ); }
  65. 65. Break apart large interfaces into smaller versions public interface RuleEngineServices { Resources getResources(); Repository getRepository(); Loggers getLoggers(); Commands getCommands(); } public interface Resources { Resource newUrlResource( URL url ); Resource newByteArrayResource( byte[] bytes ); Resource newFileSystemResource( File file ); Resource newInputStreamResource( InputStream stream ); Resource newClassPathResource( String path ); } public interface Repository { void addModule( Module module ); Module getModule( ReleaseId releaseId ); Module removeModule( ReleaseId releaseId ); } public interface Commands { Command newInsert( Object object ); Command newModify( FactHandle factHandle ); Command newDelete( FactHandle factHandle ); Command newFireAllRules( int max ); }
  66. 66. Break apart large interfaces into smaller versions public interface RuleEngineServices { Resources getResources(); Repository getRepository(); Loggers getLoggers(); Commands getCommands(); } public interface Resources { Resource newUrlResource( URL url ); Resource newByteArrayResource( byte[] bytes ); Resource newFileSystemResource( File file ); Resource newInputStreamResource( InputStream stream ); Resource newClassPathResource( String path ); } public interface Repository { void addModule( Module module ); Module getModule( ReleaseId releaseId ); Module removeModule( ReleaseId releaseId ); } public interface Commands { Command newInsert( Object object ); Command newModify( FactHandle factHandle ); Command newDelete( FactHandle factHandle ); Command newFireAllRules( int max ); } Divide et Impera
  67. 67. Be defensive with your data public class Person { private List<Person> siblings; public List<Person> getSiblings() { return siblings; } } What’s the problem here?
  68. 68. public class Person { private List<Person> siblings; public List<Person> getSiblings() { return siblings; } } person.getSiblings().add(randomPerson); What’s the problem here? Be defensive with your data
  69. 69. public class Person { private List<Person> siblings; public List<Person> getSiblings() { return siblings; } } public class Person { private List<Person> siblings; public List<Person> getSiblings() { return Collections.unmodifiableList( siblings ); } } If necessary return unmodifiable objects to avoid that a client could compromise the consistency of your data. person.getSiblings().add(randomPerson); What’s the problem here? Be defensive with your data
  70. 70. Return empty Collections or Optionals public class Person { private Car car; private List<Person> siblings; public Car getCar() { return car; } public List<Person> getSiblings() { return siblings; } } What’s the problem here?
  71. 71. Return empty Collections or Optionals public class Person { private Car car; private List<Person> siblings; public Car getCar() { return car; } public List<Person> getSiblings() { return siblings; } } What’s the problem here? for (Person sibling : person.getSiblings()) { ... } NPE!!!
  72. 72. Return empty Collections or Optionals public class Person { private Car car; private List<Person> siblings; public Car getCar() { return car; } public List<Person> getSiblings() { return siblings; } } public class Person { private Car car; private List<Person> siblings; public Optional<Car> getCar() { return Optional.ofNullable(car); } public List<Person> getSiblings() { return siblings == null ? Collections.emptyList() : Collections.unmodifiableList( siblings ); } } What’s the problem here? for (Person sibling : person.getSiblings()) { ... } NPE!!!
  73. 73. contacts.getPhoneNumber( ... ); Prefer enums to boolean parameters public interface EmployeeContacts { String getPhoneNumber(boolean mobile); }
  74. 74. contacts.getPhoneNumber( ... ); Prefer enums to boolean parameters public interface EmployeeContacts { String getPhoneNumber(boolean mobile); } Should I use true or false here?
  75. 75. contacts.getPhoneNumber( ... ); Prefer enums to boolean parameters public interface EmployeeContacts { String getPhoneNumber(boolean mobile); } Should I use true or false here? What if I may need to add a third type of phone number in future?
  76. 76. public interface EmployeeContacts { String getPhoneNumber(PhoneType type); enum PhoneType { HOME, MOBILE, OFFICE; } } Prefer enums to boolean parameters contacts.getPhoneNumber(PhoneType.HOME);
  77. 77. Use meaningful return types public interface EmployeesRegistry { enum PhoneType { HOME, MOBILE, OFFICE; } Map<String, Map<PhoneType, List<String>>> getEmployeesPhoneNumbers(); }
  78. 78. Use meaningful return types public interface EmployeesRegistry { enum PhoneType { HOME, MOBILE, OFFICE; } Map<String, Map<PhoneType, List<String>>> getEmployeesPhoneNumbers(); } Employee name Employee’s phone numbers grouped by type List of phone numbers of a give type for a given employee Primitive obsession
  79. 79. Use meaningful return types public interface EmployeesRegistry { enum PhoneType { HOME, MOBILE, OFFICE; } PhoneBook getPhoneBook(); } public class PhoneBook { private Map<String, EmployeeContacts> contacts; public EmployeeContacts getEmployeeContacts(String name) { return Optional.ofNullable( contacts.get(name) ) .orElse( EmptyContacts.INSTANCE ); } } public class EmployeeContacts { private Map<PhoneType, List<String>> numbers; public List<String> getNumbers(PhoneType type) { return Optional.ofNullable( numbers.get(type) ) .orElse( emptyList() ); } public static EmptyContacts INSTANCE = new EmptyContacts(); static class EmptyContacts extends EmployeeContacts { @Override public List<String> getNumbers(PhoneType type) { return emptyList(); } } }
  80. 80. Optional – the mother of all bikeshedding
  81. 81. Optional – the mother of all bikeshedding Principle of least astonishment??? "If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature." - Cowlishaw, M. F. (1984). "The design of the REXX language"
  82. 82. Optional – the mother of all bikeshedding Principle of least astonishment??? Wrong default
  83. 83. Optional – the mother of all bikeshedding Principle of least astonishment??? Wrong default This could be removed if the other was correctly implemented
  84. 84. API design is an iterative process and there could be different points of view ...
  85. 85. … that could be driven by the fact that different people may weigh possible use cases differently...
  86. 86. … or even see use cases to which you didn’t think at all
  87. 87. Also a good API has many different characteristics ...
  88. 88. … and they could be conflicting so you may need to trade off one to privilege another
  89. 89. What should always drive the final decision is the intent of the API … but even there it could be hard to find an agreement
  90. 90. ● Write lots of tests and examples against your API ● Discuss it with colleagues and end users ● Iterates multiple times to eliminate ➢ Unclear intentions ➢ Duplicated or redundant code ➢ Leaky abstraction API design is an iterative process
  91. 91. ● Write lots of tests and examples against your API ● Discuss it with colleagues and end users ● Iterates multiple times to eliminate ➢ Unclear intentions ➢ Duplicated or redundant code ➢ Leaky abstraction Practice Dogfeeding API design is an iterative process
  92. 92. And that’s all what you were getting wrong :) … questions? Mario Fusco Red Hat – Principal Software Engineer mario.fusco@gmail.com twitter: @mariofusco

×