API Design principle some API design principles

An API (Application Programming Interface) is a specification that defines how different software components interact with each other. An API describes a set of operations, inputs, and outputs that are independent of implementation, enabling developers to access the functionality of another program, library, or framework without knowing its underlying implementation details. APIs are a way to achieve modularity and decoupling in software systems. By defining clear boundaries and interfaces, APIs enable individual components or modules to be developed, tested, and maintained independently while ensuring interoperability between them.

Public APIs are designed for other developers or systems to be publicly available and provide a standardized way to access specific functionality. In the Java language, concepts related to public API include the public and protected modifiers. The public modifier indicates that the class, method or property is externally visible. The protected members are only visible to classes and subclasses in the same package, but are also part of the public API.

APIs in Library and Framework

I don't know if you know the difference between library and framework (the main difference lies in the relationship between active and passive). From the point of view of API, the relationship between our code and library (Library) and framework (Framework) is as follows:

For Libraries:

When we use a library, our code will actively call the API provided in the library. This means that our code makes calls to the library's API in order to implement a certain function or perform a specific task. In this case, our code is responsible for driving the program flow by calling the library's API to perform the desired action. This relationship is similar to our code being the client and the library being the server.

For Framework:

When we use a framework, the framework usually provides a set of APIs, and we need to follow the regulations of the framework to implement these APIs. In this case, the framework is responsible for driving the program flow, and our code is responsible for responding to the framework's requests. This relationship is similar to the framework being the client and our code being the server. This pattern is often referred to as the "Hollywood Principle" (Hollywood Principle) , which means "don't call us, we will call you", that is, the framework will call our code when appropriate.

History of API development

  • 1950s-60s - some simple math operations api: the whole library at that time might only consist of 10-20 functions.
  • 1970s - malloc, bsearch, qsort, rnd, I/O, system calls, formatting, early databases: during this period, APIs began to involve memory management, binary search, quick sort, random number generation, input and output operations, system Calling, formatting, and legacy databases.
  • 1980s - Graphical User Interfaces (GUIs), Desktop Publishing, Relational Databases: During this period, APIs began to support areas such as GUIs, desktop publishing technologies, and relational databases.
  • 1990s - Networking, multithreading: The API is further expanded to include network programming and multithreading.
  • 2000s - Data Structures, High-Level Abstraction, Web API: Social Media, Cloud Infrastructure: During this period, APIs began to include data structures, high-level abstraction, and Web API, covering areas such as social media and cloud computing infrastructure. (No more handwritten data structures!)
  • 2010s - Machine Learning, Internet of Things (IOT), Almost Everything: This period saw the unprecedented expansion of the capabilities of APIs, including Machine Learning, Internet of Things, and almost all modern technology areas. (So ​​much so that making a machine learning program or a mobile phone application is basically adjusting the API haha.)

API Design Principles

When we design a public API, once released, it is very difficult to change after being used by people, so once the public API is released, we can only add new functions to the library, but we cannot do the following: 1 from the library remove method. 2 Change the API protocol in the library. 3 Change the plug-in interface of the framework... and a series of operations that may affect the previous users. Sometimes when we call an API, we often see that the API needs to be deprecation (abandoned), reminding you to change to another calling method, but often you can continue to use this old version, because in order not to affect users who are using the old version of the API , they had to continue to support.

A good API should have these characteristics: easy to learn and use (even if there is no documentation, it is easy to use), difficult to misuse (otherwise sometimes misuse other APIs without reporting an error, such bugs can be found for a long time), easy to understand (Just look at the call of this API to know what it is going to do), powerful functions that can meet the needs, easy to develop and expand, suitable for the target audience...

When designing an API, it is very important to write API usage examples as early as possible and frequently. First, modify and polish the documents and interfaces over and over again, instead of writing the implementation first, so as to avoid wasting the completed implementation work by modifying the design. We don't start writing until the specification is fully settled.

Information hiding information hiding

This is the basic requirement of the API. You only need to tell the user how to use the interface and what functions it can achieve, without disclosing any implementation details. We can make classes and members private through encapsulation, exposing only the necessary public interfaces.

Avoid throwing specific exceptions: For example, if an API for obtaining user information throws a SQL exception, then the user may know where the data exists, what fields are there, and so on. Of course, some instructive exceptions mentioned in the documentation can be thrown to help users modify and use.

In the documentation, also avoid mentioning implementation details: such as "finally use the hashcode() method to return the XX value" and so on.

When a class implements Serializablethe interface: all fields of the class (including private fields) are included in the serialized form. This may lead to some implementation details being exposed, because in the process of serialization and deserialization, it is necessary to know the type and structure of these fields and how to read and write them. In order to avoid exposing implementation details, the following methods can be used: 1. In the class, explicitly define which fields need to be serialized and which fields do not need to be serialized. The keyword can be used transientto mark fields that do not need to be serialized. 2. Consider using other serialization schemes, such as JSON or XML, which allow finer-grained control over the output format and exposed data.

Conceptual Weight Conceptual burden

Conceptual burden refers to the number and difficulty of concepts a programmer needs to learn to use an API. Conceptual burden is more important than the "physical size" of an API, because it directly affects how easy it is for a programmer to learn and use an API.

Conceptual burden can be defined as the number and difficulty of new concepts in the API, that is, the space occupied by the API in the user's brain. The larger the space required, the harder it is to remember and use. During the API design process, some modifications may add a lot of functionality with little conceptual burden, and these are fine, for example:

  1. Add overloaded methods that behave consistent with existing methods.
  2. Add arccos function on the basis of existing sin, cos and arcsin.
  3. Add new implementations for existing interfaces.

One of the goals of designing an API is to achieve a high power-to-weight ratio , allowing the programmer to do more work with fewer concepts. To achieve this goal, API designers should minimize the conceptual burden and make the API easier to learn and use.

Naming

Naming is one of the most important factors in the usability of API. For users, API is a little language. When designing API, our main goal is to make client code easy to read, express clearly and write smoothly. To achieve these goals, we need to follow the following principles to name API components:

  1. The name should be as self-explanatory as possible, so that users can understand its function when they see the name.
  2. Leverage existing knowledge, follow common naming conventions and conventions, and reduce learning costs.
  3. The names should be harmonious and consistent with the programming language used to achieve overall harmony.

Follow the "principle of least astonishment", let users know what the API does when they see it, and ensure that the behavior of the API is consistent with the user's expectations when actually using it, avoiding confusing naming and design.

When naming your API, make sure to follow these guidelines:

  1. Be consistent: Don't use the same word for more than one meaning, and don't use more than one word for the same meaning. computeX()For example, don't use both and generateX(), or deleteX()and and in the same API removeX(), as they may confuse users. (Generally speaking, deleteX()it means to permanently delete an object or data from storage or data structure. removeX()It means to remove an object from a collection or data structure, but it does not necessarily involve permanent deletion. In short, choose the most appropriate and then keep it consistent.) In addition to the use of words to be consistent, the order of parameters in similar methods should also be consistent. For example, in this method, write source first and then write destination, and another similar method should also write source first. .

  2. Avoid obscure abbreviations : A concise and easy-to-understand name can help users understand the function of the API more quickly. For example, names like Set, PrivateKey, Lock, ThreadFactoryand are easier to understand than names like , and .Future<T>DynAnyFactoryOperationsENCODING_CDR_ENCAPSOMGVMCID

  3. Choose names related to abstract concepts : Good naming should be closely related to abstract concepts in the API, which helps users better understand the functions and usage methods.

Some customary norms for reservations:

  • CamelCase: For example, get_x() and getX(), one uses an underscore to separate the verb and attribute, and the other uses camelCase. In most programming languages, camel case names such as getX().
  • Class names should use nouns: such as BigInteger, PriorityQueue; interface names should use nouns or adjectives: such as Collection, Comparable.

  • Non-mutating methods (methods that do not change the state of an object) should use nouns, copulas, or prepositions: such as size, isEmpty, plus; mutating methods (methods that change the state of an object) should use verbs: such as put, add, and clear.

  • Strive for consistency: If there are two verbs and two nouns in an API, a programmer might expect all four combinations to exist. For example: addRow, removeRow, addColumn, removeColumn

In short, the naming of the API is a very important thing, and it often takes a lot of time to first name it. This requires constantly listing alternative names, and choosing the best naming by actually writing use cases and discussing decisions.

Some other API design considerations

  • Write good documentation for the API. Although our API has good names to help users understand, we still need to write a good document to help users use it. A good API document should ensure that the following points are covered: write a brief description for each public class, explaining its function and purpose; write a detailed description for each public method, including its function, parameters, return value and exceptions that may be thrown; Write a description for each public field, explaining its purpose and scope; provide a description for each method parameter, explaining its purpose and expected input; provide a clear behavior specification for the API, so that users can understand the expected behavior and limitations of the API. "we won't see the components reused without good documentation."--DL Parnas.
  • Avoid too many parameters , three or less is good, too many users are difficult to use. (In order to shorten the parameter list, you can use the split method: split a method with many parameters into multiple methods with fewer parameters. Or combine parameters, encapsulate related parameters into a single object, and then The object as a parameter of the method.
  • Reduce exceptions for users to handle . 1. Return an empty list instead of null. For example, we write an API that can return a list of all cities that are currently raining, but if all cities are not raining, what should we return? Is it null? No! An empty list is more suitable than null , so that even if there is no rain in the city, it will not affect the traversal operation after the user gets the list. If it returns null, the user is forced to deal with the return value of null separately, which is uncomfortable. 2. If there is a more suitable type, avoid returning the String type. Suppose there is an API method that returns a date value. If it returns a string type, there may be many different date formats, such as "2023-05-06", "May 6, 2023" and so on. This requires the caller to understand and process these formats, increasing the possibility of error and the cost of understanding. And if you use a specialized date type (such as in Java LocalDate), the caller can more easily process and manipulate date values, because they are already more specific and clearly structured data.
  • If an unexpected event occurs, it must be reported quickly, promptly and prominently . Like in the code below, Propertiesthe class inherits HashTable, but Propertiesthe instance needs to map strings to strings. Will throw when the method is called saveif the instance contains a key or value that is not of type string . This means that errors are only detected when calling the method, not when trying to insert an illegal key or value. To fix this, override the method to only accept string-type keys and values. This way, when an attempt is made to insert a non-string key or value, an exception can be thrown immediately and the user notified of the error, instead of being thrown later in the method call. This example seems very simple, but many times developers really fail to consider this situation, and do not stop errors in time, and only talk about them at the end, resulting in poor user experience.PropertiesClassCastExceptionsaveputsave
// A Properties instance maps Strings to Strings
public class Properties extends HashTable {
 public Object put(Object key, Object value);
 // Throws ClassCastException if this instance
 // contains any keys or values that are not Strings
 public void save(OutputStream out, String comments);
}

summary

This article talked about the concept of api, its development history, and introduced some API design principles. Not one article can cover it all, but some of the more common and fundamental designs proposed here can still serve as a good inspiration.

Guess you like

Origin blog.csdn.net/weixin_44492824/article/details/130538480