DEV Community

Cover image for API Design: In The Wild (part 2)
Sam Rose
Sam Rose

Posted on • Originally published at samwho.dev on

API Design: In The Wild (part 2)

In a previous post we looked at some real-world APIs, highlighting the good and the bad, and in this post we’re going to do the same!

  • Python’s datetime.datetime
  • Java’s URL.equals method
  • Go’s standard library
  • The word “filter”
  • Conclusion

Python’s datetime.datetime

Most experienced Pythonistas have written something like this at some point in their career:

import datetime
now = datetime.datetime.now()
print(now)

While not incorrect, the repeated naming is jarring.

Go specifically calls this out in a blog post on package naming, and I don’t think I could possibly improve on it so I’ll quote it verbatim:

Avoid stutter. Since client code uses the package name as a prefix when referring to the package contents, the names for those contents need not repeat the package name. The HTTP server provided by the http package is called Server, not HTTPServer. Client code refers to this type as http.Server, so there is no ambiguity.

You can combat the stuttering somewhat using Python’s from x import y syntax:

from datetime import datetime
now = datetime.now()
print(now)

As a fun little extra, a friend sent me the following bit of Ruby:

files = Dir.entries(Dir.pwd)
files.select! { |file| File.file?(file) }

Finding Nemo "Mine" meme but using "file" instead

Java’s URL.equals method

If you were going to test two URLs for equality, how might you do it?

It’s completely reasonable to do a comparison of the string representations. ”https://google.com” != “https://facebook.com”. This isn’t the road the original authors of Java’s URL class took, though.

import java.net.URL;

public final class Main {
    public static void main(String... args) throws Exception {
        URL a = new URL("https://google.com");
        URL b = new URL("https://google.com");
        System.out.println(a.equals(b));
    }
}

Does this always return true?

Sadly not.

java.net.URL.equals, through a long chain of indirection, ends up calling java.net.InetAddress.getByName for both of the URLs, which performs a DNS lookup in order to check that they resolve to the same IP address. It does have a cache, but if you’re super unlucky and the cache expires between the two lookups, it’s possible for you to get two different IP addresses for the same hostname if that hostname uses DNS round robin, which is common in 2019.

Similar to java.io.File.exists, this method doesn’t quite do what it advertises to do. Because of this, and the fact it makes a blocking network call, it’s flagged by static analysis tools.

Go’s standard library

I hesitated to add this section, as it’s fairly controversial, but I do think there’s a real problem here.

Go’s standard library is missing some key things I’d expect to find in a standard library. The desire to keep the language implementation and usage simple has shifted burden from the language implementor to the language user.

For example, checking that a list has a specific element is not something the language provides for you. If you were to look it up, the attitude you find is that methods like this are trivial to write. While true, I find myself frequently looking up and copy-pasting trivial methods on a weekly basis.

Efforts to add common operations to the standard library are appreciated, but often clunky. Getting an absolute value requires you to cast to and from a float64. Sorting an array requires you to sacrifice type safety and supply your own swapping function. Creating a big int has a method per type that you can create them from, appending the type to the method name.

The addition of generics and method overloading, while complicating the language and its implementation, would make life easier for users of the language.

The word “filter”

How would you define the word “filter” in general use? Most people, myself included, thinking of removing things. Impurities from metal, dirt from gold, water from pasta.

So what does this return?

list(filter(lambda n: n > 5, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))

If you guessed “a list containing all numbers greater than 5”, you would be correct.

I find this confusing. I have to check which way filter works almost every time I use it, even though in the research I did for this post I learned that filter works the same way in every language I checked.

I like the approach Ruby takes to this problem:

list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list.select { |n| n > 5 } # [7, 8, 8, 9]
list.reject { |n| n <= 5 } # [0, 1, 2, 3, 4, 5]

select and reject are more immediately obvious, and good general words for these use cases. At least they’re better than filterfalse.

Conclusion

Another post, another set of real-world API examples and suggestions on improving them.

I’d love to hear your thoughts, and if you have any APIs you love or hate I’d love to hear about them as well!

Top comments (0)