Bad API design happens because of one reason: developers are focussed on functionality and not on maintainability or usability. Sufficiently advanced APIs are those whose effects are indistinguishable from magic — they have minimal interfaces, they’re easy to use, et cetera — and can be called “Sufficiently Advanced Technology”, or S.A.T. The best known S.A.T. in Perl is “use strict” — as Damian says, “use it and magically all of your bugs appear.” It’s got a very minimal interface, and it’s extremely simple to use. A better example is “use diagnostics”, which gives you better diagnostics on bugs than “use warnings” gives you, and all you need is three extra characters.

This tutorial discusses the seven principles that developers can use to deliver a better API design. In this text the seven principles will be in bold text for easier reading.

The first principle is do one thing really well. You usually don’t need huge APIs to solve a problem, and more often than not doing one little thing will be more useful in the long run than coming up with a complicated function. A good example of this is the Perl6::Say module, which brings the ‘say’ command from Perl 6 to Perl 5 (and it will be backported to Perl 5.10). All it does is it works exactly like the ‘print’ command, but automatically puts a newline character on the end. So why should you give such a simple task its own interface? So you can forget the internals. Besides, just because something is trivial doesn’t mean it’s not important.

Another good example is the Perl6::Slurp module, which slurps up the contents of a file into a variable. This helps prevent this sort of behaviour:

my $text = `cat $filename`;
my $text = join "", <$fh>;
my $text; $text .= $_ for <$fh>;
my $text .= $_ for <$fh>;
my $text = do { local $/; <$fh> };

The last line is actually the “correct” way to do it, but as Damian says, “It’s gonna scare children… or Java programmers.” Any of these lines can be replaced with my $text = slurp $fh; or, if you’d rather just get a filename, my $text = slurp "filename";. Because it’s simple it’s harder to get wrong.

The second principle of better API design is design by coding. You should make interfaces based on the input from people who are going to be using that interface. If you’re going to be using the interface, don’t design the interface first, write the code that’s going to use the interface. This prevents doing things like making bit-handling code that can only get and set bits, when people using the code would like to be able to flip bits or dealing with bytestreams. Ask the users what they’d like to see, and ask as many people as possible what they’d like to see. Asking only one or two people is dangerous because you might be asking outliers. Two modules that were designed by looking at the use cases first were Regexp::Common and Contextual::Return.

The third principle of better API design is evolve by subtraction. S.A.T. doesn’t just appear out of thin air, it evolves. To obtain S.A.T., identify clunky bits of your interface and eliminate them. An easy way to do this is to let a small subset of your potential userbase use the module first. That way they can tell you what the clunky bits are.

Damian used IO::Prompt as an example of this. When he first developed it, he had a few options in there (-bool and -chomped) that he found he was using fairly frequently. By examining his code, he found that -chomped was used about 80% of the time, so he made that behaviour the default. Similarly with -bool, it was eliminated by using Contextual::Return. You should try to find clever ways to automatically do the things your users are doing most often. Obviously this won’t work if you’ve got two equally-sized (or equally-verbose) groups of users that use disparate behaviour, so you might consider making two modules, each giving the behaviour the specific group of users is expecting.

The fourth principle of better API design is declarative beats imperative. Allow the user of your code to say what they want, not how to do it. Don’t make the user set up complex data structures (or even not-so-complex data structures) in order to use your module. A good example of bad API design from this point-of-view is the Exporter module, where you have to set up variables like @EXPORT and @EXPORT_OK to get it to do what you want. Instead of using Exporter, you should use a module like Perl6::Export::Attrs, where you say you want a method exported right there in the method declaration. Another example would be something that interacts with a database. Instead of code that’s filled with SQL statements, you could write a wrapper method that takes the information that the user gives, generates the SQL, queries the database, and returns the results.

The fifth principle of better API design is preserve the metadata. In other words, don’t piss off your users. Don’t insist that they tell you the same information over and over and over again. A program should only need to be told something once, and it should remember that. A good example of a module that violates this principle is the majority of configuration file parsers on CPAN. Most of them trash any comments in config files if they’re read in and written out again. Some of them reorder options as well. Config::Std fixes both of these problems by caching a structured representation of the file it’s read in.

The sixth principle of better API design in leverage the familiar. Your users may not know a lot about your module, but odds are very good that they know something about the idioms of whatever language they use. Take advantage of this by creating an interface that’s similar to something they’ll already know. A good example of this is a module that logs information. The majority of logging modules on CPAN use an object-oriented interface that Damian derides as being overkill: “it’s just logging!” If you have complicated logging requirements then you can use a complicated logging tool or module, but if you just want simple logging, use a simple logging tool or module (or, as Damian did, write your own). Logging is essentially a combination of “print with fanciness” and “print conditionally”, so leverage print! The same thing for the say command introduced in the Perl6::Say module. Since it does something extremely similar to print, its interface is extremely similar (it doesn’t support a few things that print does, but it’s close).

The seventh and final principle of better API design is that the best code is no code at all. Ideally you just use the module and shit happens, and it’s good shit that happens. If you’re lucky it’s excellent shit. A good example of this is the Object::Dumper module, which adds default stringification and numerification to objects. To use it, all you have to do is use Object::Dumper; and all of your objects have numerification and stringification routines. This helps prevent doing things like:

  my $index_val = FuzzyNum->new( 3.14, plus_minus => 0.001685 );
  print $dataset[$index_val];

What you really want when this code is run is instant death. What you get (without Object::Dumper) is that the $index_val stringifies into the hash memory location of the object, which is probably a big number, so you end up with an extremely large array. As Damian says, “At this point it would be nice if Perl played ‘Girl From Ipanema’ for ten minutes… and then core-dumped.” Object::Dumper provides security so that the above code will die instantly. And it’s a zero-effort module, as you only need to load the module at the top of your code.

In conclusion, two of Perl’s greatest strengths are it does a lot of the hard work for you and it doesn’t get in your way. Make those the strengths of your APIs too — hoist the hard work inside your interface and don’t make the users do the hard work. Consider the interfaces to your APIs by asking yourself some questions: What could you remove? What could you automate? What could your code infer? If you can find better defaults for subroutines by examining how your subroutines are used, use those defaults. Try to give your subroutines fewer options. Design subroutines that do one useful thing exceptionally well. Think about overloading objects, and tying variables and filehandles. Develop tools that let you code without thinking, without conscious effort, preferably without any effort at all.

Popularity: 10% [?]

Related Posts:

  • No Related Post