Feed Icon RSS 1.0 XML Feed available

Cleaner API Design Using Ignorable "Hints"

Date: 12-Aug-2005/19:51

Tags: , ,

Characters: (none)

Sometimes API authors expose additional entry points to their code which exist only because of performance. For instance, look at why MakeManyWidgets() exists in the sample below:
class WidgetFactory
{
private:
   // a really, really slow routine
   void StopProcessesSoItsSafeToMakeWidgets();

   // another really, really slow routine
   void RestartProcesses();

public:
   Widget* MakeAWidget() {
      StopProcessesSoItsSafeToMakeWidgets();
      Widget* w = new Widget();
      RestartProcesses();
      return w;
   }

   std::vector<Widget*> MakeManyWidgets(int HowMany) {
      std::vector<Widget*> ret;
      StopProcessesSoItsSafeToMakeWidgets();
      for (int temp = 0; temp < HowMany; temp++) {
          ret.push_back(new Widget());
      }
      RestartProcesses();
      return ret;
   }

   void DoSomethingElse() {
      assert(ProcessesRunning());
      return;
   }
};
The extra routine was added as a mere performance convenience, since making N widgets has identical semantics to simply N successive calls to MakeAWidget(). This is presumably safer than publishing the private routines for starting and stopping processes so it's safe to make widgets--not only might that be an implementation detail we don't want to expose, it could be deemed too serious a problem if the client improperly matches up stop/start calls.
I've seen this pattern countless times, and never liked it. Routines like MakeManyWidgets() lead the clients of your API to start disrupting the control flow in their programs to try and get a performance payoff that may turn out to be irrelevant in the future. It also gives the misleading impression that there might be a semantic significance to creating a set of widgets as a batch, and will make source code written to the API a lot harder to absorb.
If I face a situation like this, I completely decouple the performance "hint" from the routines that do the work. As a rule, I also make sure that if the hint gives blatantly incorrect information, the worst that can happen is that your program is a bit slower than it would have been without the hint. To give an example of how this might work, look at HINT_MakingManyWidgets() below:
class WidgetFactory
{
private:
   mutable int widgets_hint;

private:
   // a really, really slow routine
   void StopProcessesSoItsSafeToMakeWidgets();

   // another really, really slow routine
   void RestartProcesses();

public:
   void HINT_MakingManyWidgets(int HowMany) const {
      widgets_hint += HowMany;
   }

   Widget* MakeAWidget() {
      if (ProcessesRunning()) {
         StopProcessesSoItsSafeToMakeWidgets();
      }

      Widget* w = new Widget();
      if (widgets_hint > 0) {
         widgets_hint--;
         RestartProcesses();
      }

      return w;
   }

   void DoSomethingElse() {
      if (!ProcessesRunning()) {
         widgets_hint = 0;
         RestartProcesses();
      }
   }
};
This way, developers aren't encouraged to complicate their code up front. No one will use these HINT_ functions unless they need to--only those clients who are dissatisfied with performance will bother. You can add as many as you like, adapted specifically to suit the real use cases of important clients. And if you ever want to stop supporting a hint you merely make the function have no effect.
The worst you'll do to your clients is slow them down, and your API and code using it will be purer and more elegant!
Business Card from SXSW
Copyright (c) 2007-2018 hostilefork.com

Project names and graphic designs are All Rights Reserved, unless otherwise noted. Software codebases are governed by licenses included in their distributions. Posts on blog.hostilefork.com are licensed under the Creative Commons BY-NC-SA 4.0 license, and may be excerpted or adapted under the terms of that license for noncommercial purposes.