Codementor Events

What are the qualities of a good Application Programming Interface/Function?

Published Mar 04, 2023
What are the qualities of a good Application Programming Interface/Function?

Originally published on www.programminggyan.com.

An Application Programming Interface(API) might not be as appetizing as your favorite dish, but it sure is a crucial component of modern-day software applications. It’s like the grease that keeps the wheels of different software systems running smoothly.

So, what is an API, you ask? Well, it’s essentially a bridge that connects different software components and allows them to communicate with each other seamlessly. Think of it as a translator that ensures all parties involved are speaking the same language.

To make it simpler, let’s take a real-life example. Imagine you’re at a restaurant, and you want to order your favorite dish. You don’t go to the kitchen and tell the chef what you want, do you? No, you tell the waiter who then goes to the kitchen and relays your order to the chef. In the same way, an API acts as the waiter in this scenario. It provides developers with a menu of available functions, data, and resources that they can choose from to build their applications.

The best part about an API is that it shields developers from the underlying complexity of the system it’s interfacing with. It’s like a magical cloak that hides all the intricacies of the system, making it easier for developers to use. They don’t have to worry about the nitty-gritty details of how the different components work together. All they need to know is how to use the API, and they’re good to go.

There are several types of APIs, including:

  1. Web APIs: These are APIs that enable communication between different web services or applications over the internet.
  2. Operating System APIs : These are APIs that provide access to the operating system’s functionality, such as file management, network communication, and system configuration.
  3. Library APIs : These are APIs that are part of a programming language or library and provide pre-written code for common tasks, such as data sorting or encryption.
  4. Hardware APIs : These are APIs that allow software applications to communicate with hardware devices, such as cameras, printers, or sensors.
  5. Database APIs : These are APIs that allow software applications to access and manipulate data stored in databases.
  6. Cloud APIs : These are APIs that provide access to cloud services, such as storage, computing power, and machine learning algorithms.

factorial APIfactorial API

Who doesn’t love a good factorial calculation? Okay, maybe not everyone, but it sure is an essential part of computer science and mathematics. And in this example, we’re using a recursive approach to get the job done.

But let’s be real, do users really care about the internal workings of the function? Probably not. They just want a simple and reliable way to calculate factorials for any number. That’s where APIs come in.

So, in this case, we can wrap our factorial function into an API and give users a simple way to calculate factorials without worrying about the nitty-gritty details of the function. They can just call the API with their desired number, and voila! The API does the heavy lifting for them.

But here’s the thing, an API is only as good as its design. It needs to be well-documented, regression tested, and stable between releases. You don’t want your users pulling their hair out because your API keeps breaking every time you release a new version.

Why Should You Write APIs?

Here are a few good reasons why you should consider writing APIs:

  • Hides implementation : APIs allow you to hide the implementation details of your module, giving you the flexibility to change the implementation without causing chaos for your users. This means that you can update and improve your software over time, without breaking the user experience.
  • Increases longevity : Systems that expose their implementation details tend to become a mess of interdependent code over time. However, APIs can evolve over time, allowing new features to be added while maintaining backward compatibility with older versions. This promotes longevity and sustainability in your codebase.
  • Promotes modularization : When you build your application on top of a collection of APIs, you promote a loosely coupled and modular architecture. The behavior of one module is not dependent on the internal details of another module, making it easier to manage and maintain your code.
  • Reduces code duplication : By keeping all your code’s logic behind a strict interface that all clients must use, you centralize the behavior in a single place. This helps to reduce code duplication, making your codebase more organized and efficient.
  • Easier to change the implementation : With APIs, you can change the implementation details of your module without affecting any code that depends on the API. This makes it easier to optimize and improve the performance of your software without disrupting the user experience.
  • Easier to optimize : With the implementation details hidden, you can optimize the performance of your API without requiring any changes to your clients’ code. This makes it easier to improve the performance of your software over time, without affecting the end-users.

What are the basic qualities of a good API?

Model the Problem Domain

Model the Problem DomainModel the Problem Domain

Let’s give this API a big thumbs up for being a great example of how to create a good abstraction! When designing an API, it’s important to think about the people who will be using it, and this one hits the mark by being clear and easy to understand for both programmers and non-programmers alike.

Here are a few reasons why:

  • High-level concepts : The SmartLight API is designed in terms of high-level concepts like “isOn”, “turnOn”, “turnOff”, and “setColor”. These are all terms that make sense to anyone who has ever used a light bulb, making it easy for users to understand what each function does.
  • No implementation details : The API hides all the low-level implementation details that would be confusing or irrelevant to users. Users only need to know what the function does and how to use it, not how it’s implemented under the hood.
  • No implementation details : The API hides all the low-level implementation details that would be confusing or irrelevant to users. Users only need to know what the function does and how to use it, not how it’s implemented under the hood.
  • Simple and intuitive : The API is easy to use and understand, even for non-programmers. It’s intuitive, so users can easily interact with the SmartLight device without needing any special training or technical expertise.

Overall, this API sets a great example for how to create an API that is easy to use, easy to understand, and provides a good abstraction.

Hide Implementation Details

Let’s talk about the importance of hiding implementation details in APIs, which can be done in two ways: physical hiding and logical hiding.

Physical hiding involves separating internal details from the public interface by storing them in a separate file, while logical hiding uses encapsulation to restrict access to internal members of an object.

Encapsulation provides a mechanism for limiting access to members of an object by defining them as public, protected, or private. By separating the public interface of an API from its underlying implementation, encapsulation helps promote modularity, maintainability, and code reusability.

Hide Implementation Details

Get ready to connect and communicate with the world of IoT! The IoTDevice class is the gateway to all the functionality you need to send and receive messages over a network. But don’t worry, you don’t need to be an expert in network communication to use it. With the power of physical and logical hiding, the implementation details are hidden from you, making it easy to use and understand.

Physically, the implementation details of the IoTDevice class are separated from the public interface using the Pimpl idiom. This means that the details of how the device connects to the network and how it sends and receives messages are stored in a separate implementation file that is not visible to the user. This way, you don’t need to worry about the nitty-gritty details of the implementation and can focus on using the interface.

Logically, the IoTDevice class is designed with encapsulation in mind. It only exposes the necessary methods for connecting, sending, and receiving messages, while keeping the implementation details hidden. This makes the interface easy to understand, even for non-programmers. Plus, all the implementation details, including methods, classes, and variables, are hidden from you. This not only makes the interface user-friendly, but also reduces the risk of misuse.

So, whether you’re building an IoT project or just want to explore the world of network communication, the IoTDevice class is your go-to interface. It’s easy to use, easy to understand, and with physical and logical hiding, you can trust that the implementation details are safely hidden away.

Minimally Complete

Let’s talk about creating a minimally complete API that is both easy to use and maintain. To achieve this, you must start by gathering requirements and modelling use cases. This ensures that you have a clear understanding of what your API is expected to do.

One essential aspect of a good API is a compact interface that is easy to understand and remember. The goal is to make it so simple that users can quickly grasp the core functionality without being overwhelmed.

It’s crucial not to overpromise when developing an API. While adding new functionality is easy, removing it is difficult. Thus, you must minimize the number of public classes and functions in your API to prevent any issues later on.

When it comes to virtual functions, be careful. While they offer flexibility, they can also lead to problems such as breaking client code or creating unsafe conditions. Consider using a class with no virtual functions, as it is typically more robust and easier to maintain.

If you decide to allow subclassing, ensure that your API is designed to allow it safely. This means following rules such as declaring your destructor to be virtual and documenting how methods of your class call each other. Never call virtual functions from your constructor or destructor.

Convenience APIs are another way to simplify your API for users. These APIs encapsulate multiple API calls to provide higher-level operations. However, it’s important to keep your core and convenience APIs separate to maintain clarity and ease of use.

Minimally Complete

Meet the IoTDevice class – your one-stop shop for controlling your IoT device with ease! This API is designed to provide all the necessary functions for controlling the device in the most efficient and user-friendly manner possible.

With a simple and concise interface, the IoTDevice class provides functions for connecting, disconnecting, starting, stopping, and resetting the device. But why stop there? The convenience APIs let you set the temperature and humidity levels of the device with a breeze.

But don’t be fooled by its simplicity. This API is designed with encapsulation in mind. The implementation details are hidden from the user, ensuring the physical hiding of the implementation.

What’s more, the API is designed to be minimally complete, ensuring it only provides the necessary functions for controlling the device. No over-promising here! And when virtual functions are added, they’re done judiciously, only if necessary, and with the utmost care.

Easy To Use

A good API should make simple tasks easy and obvious. Nobody wants to spend hours wading through documentation just to connect to an IoT device, right? That’s why a well-designed API should provide clear and concise functions for controlling the device, with minimal unnecessary complexity.

Speaking of documentation, a good API should also make the task of writing it much easier. By providing intuitive and consistent function naming conventions and parameter ordering, you’ll save yourself and your users a lot of headaches. Plus, if your API is designed well, you won’t need to write as much documentation to begin with!

But that’s not all. A good API should also be difficult to misuse. One way to accomplish this is by using enums instead of booleans, which improves code readability. And if you must use multiple parameters of the same type, make sure to distinguish them clearly with descriptive names.

Consistency is key when it comes to designing an API. This means using the same words for the same concepts across the API, and sticking to a consistent design approach. By doing so, you’ll make your API easy to remember and adopt.

Orthogonality is another important characteristic of a well-designed API. When functions don’t have side effects, it means that calling a method that sets a particular property should only change that property, and not anything else. Additionally, reduce redundancy by ensuring that the same information isn’t represented in more than one way. This will help establish a single authoritative source for each piece of knowledge.

Finally, make sure your API is platform-independent. This means avoiding platform-specific code in your public headers, as it exposes implementation details and makes your API appear different on different platforms. Nobody wants that.

EASY TO USE 

Ah, let’s talk about this code snippet! It’s always exciting to dive into some C++ code and explore its intricacies. Here, we have an abstract class called IoTDevice that defines a set of virtual functions that any derived class must implement.

Firstly, let’s take a look at the enum class Status. It defines two possible statuses of an IoT device – ONLINE and OFFLINE. It’s always a good practice to use enums instead of booleans to improve code readability. It helps avoid confusion when dealing with boolean values and ensures that the code is self-explanatory.

Moving on, we have four pure virtual functions: getStatus(), connect(), disconnect(), and sendCommand(). These functions define the basic functionality of an IoT device. The getStatus() function returns the status of the device, while connect() and disconnect() functions control the connection and disconnection of the device. The sendCommand() function sends a command to the device.

These functions are pure virtual, which means they must be implemented by any derived class. This makes the class an abstract class, and you cannot create an instance of this class directly. It’s a great way to ensure that the derived classes have a consistent interface and follow a set of rules.

Lastly, we have a static function called create(). It creates and returns a unique_ptr to a new IoTDevice object. It takes in a device name as a parameter, which can be used to create the appropriate derived class object based on the device type. It’s an elegant way to create objects dynamically and helps maintain the code’s platform independence.

Overall, this code snippet is a well-designed API that follows several best practices like consistent function naming, avoiding platform-specific code, and using enums instead of booleans. The virtual functions ensure that any derived class follows a set of rules, and the create() function helps create objects dynamically.

Loosely Coupled & High in Cohesion

Let’s delve into the fascinating world of software engineering principles and explore the concepts of cohesionand coupling. Cohesion is the idea that the components within a module work closely together towards a common goal, while coupling refers to the degree to which components depend on one another. When a system has high cohesion and low coupling, it is easier to maintain, test, and modify over time.

There are several measures that can be used to evaluate the degree of coupling between components. The size of a component, including the number of classes, methods, and arguments per method, is one such measure. The visibility of a connection and the directness of the connection also play a role in determining the level of coupling. Another important consideration is flexibility, or how easy it is to change the connections between components.

If class A only needs to know the name of class B and doesn’t need to call any of its methods or know its size, a forward declaration can be used instead of including the entire interface, reducing coupling between the two classes.

Callbacks, observers, and notifications are other solutions to reduce coupling between components. Callbacks are pointers to functions within module A that can be invoked by module B at the appropriate time. Observers and notifications involve building a centralized mechanism to send notifications or events between unconnected parts of the system.

Loosely Coupled & High in Cohesion

This is a fascinating piece of code that is using forward declaration to reduce coupling between classes! Let’s dive in and see what we can learn.

So, we have a Sensor class here that represents an IoT sensor capable of reading values. The class has private member variables that are hidden from the outside world to protect the implementation details. These member variables include pin, which is an integer representing the pin the sensor is connected to, threshold, which is a float representing the minimum change in value required to trigger a callback, lastValue, which is the previous value read from the sensor, and callback, which is a pointer to a function that will be called when the sensor value changes.

The Sensor class also has a private method called notifyChange(), which is used to notify the callback function of a value change. This method is only called if a callback function has been registered.

The constructor takes in three parameters: pin, threshold, and callback, and initializes the private member variables accordingly. The readValue() method reads the current value from the sensor, updates the lastValue member variable, and then checks if the value has changed by more than the threshold value. If it has, the notifyChange() method is called to notify the callback function.

Overall, this code is well-designed and demonstrates good coding practices such as encapsulation, information hiding, and low coupling. The use of a callback function to notify external code of a value change is an excellent way to decouple the Sensor class from the code that uses it.

Conclusion

In our discussion, we covered several key points related to API design best practices:

  • API design is crucial for creating a stable, easy-to-use, and scalable software system.
  • A good API should be well-documented, with clear naming conventions and consistent use of terminology.
  • A good API should be intuitive and easy to learn, with minimal learning curve and straightforward implementation.
  • A good API should be flexible and extensible, allowing for changes and updates over time without breaking backward compatibility.
  • A good API should be secure, with appropriate access control and authentication mechanisms in place.
  • A good API should be optimized for performance, with efficient data structures and algorithms.
  • API design should involve input from developers, users, and stakeholders, with an emphasis on usability, functionality, and overall business goals.

In conclusion, an API is a powerful tool that enables different software systems to work together seamlessly. It simplifies the process of building software applications and makes it easier for developers to focus on what they do best – creating awesome applications that make our lives better. So, the next time you use an application, remember to thank the API that made it all possible.

Book Recommendation

Discover and read more posts from Dheeraj Jha
get started
post commentsBe the first to share your opinion
Show more replies