Published by Marco on 23. May 2014 18:21:24
Updated by Marco on 30. May 2014 08:29:38
------------------------------------------------------------------------

A big part of an agile programmer's job is API design. In an agile project, the
architecture is defined from on high only in broad strokes, leaving the fine
details of component design up to the implementer. Even in projects that are
specified in much more detail, implementers will still find themselves in
situations where they have to design something.

This means that programmers in an agile team have to be capable of weighing the
pros and cons of various approaches in order to avoid causing performance,
scalability, maintenance or other problems as the API is used and evolves.

When designing an API, we consider some of the following aspects. This is not
meant to be a comprehensive list, but should get you thinking about how to think
about the code you're about to write.

[Reusing Code]

  * Will this code be re-used inside the project?
  * How about outside of the project?
  * If the code might be used elsewhere, where does that need lie on the time
    axis?
  * Do other projects already exist that could use this code?
  * Are there already other implementations that could be used?
  * If there are implementations, then are they insufficient?
  * Or perhaps not sufficiently encapsulated for reuse as written?
  * How likely is it that there will be other projects that need to do the same
    thing?
  * If another use is likely, when would the other project or projects need your
    API?

[Organizing Code]

  * Where should the API live in the code?
  * Is your API local to this class?
  * Is it private?
  * Protected?
  * Are you making it public in an extension method?
  * Or internal?
  * Which namespace should it belong to?
  * Which assembly?

[Testing Code]

  * What about testability?
  * How can the functionality be tested?

Even if you don't have time to write tests right now, you should still build
your code so that it can be tested. It's possible that you won't be writing the
tests. Instead, you should prepare the code so that others can use it.

It's also possible that a future you will be writing the tests and will hate you
for having made it so hard to automate testing.

[Managing Dependencies]

  * Is multi-threading a consideration?
  * Does the API manage state?
  * What kind of dependencies does the API have?
  * Which dependencies does it really need?
  * Is the API perhaps composed of several aspects?
  * With a core aspect that is extended by others?
  * Can core functionality be extracted to avoid making an API that is too
    specific?

[Documenting Code]

  * How do callers use the API?
  * What are the expected values?
  * Are these expectations enforced?
  * What is the error mechanism?
  * What guarantees does the API make?
  * Is the behavior of the API enforced?
  * Is it at least documented?
  * Are known drawbacks documented?

[Error-handling]

This is a very important one and involves how your application handles
situations outside of the design.

  * If you handle externally provided data, then you have to handle extant cases
  * Are you going to log errors?
  * In which format?
  * Is there a standard logging mechanism?
  * How are you going to handle and fix persistent errors?
  * Are you even going to handle weird cases?
  * Or are you going to fail early and fail often?
  * For which errors should your code even responsible?
  * How does your chosen philosophy (and you should be enforcing contracts) fit
    with the other code in the project?

[Fail fast; enforce contracts]

While we're on the subject of error-handling, I want to emphasize that this is
one of the most important parts of API design, regardless of which language or
environment you use. [1]

Add preconditions for all method parameters; verify them as non-null and verify
ranges. Do not catch all exceptions and log them or -- even worse -- ignore
them. This is even more important in environments -- I'm looking at you
client-side web code in general and JavaScript in particular -- where the
established philosophy is to run anything and to never rap a programmer on the
knuckles for having written really knuckle-headed code.

You haven't tested the code, so you don't know what kind of errors you're going
to get. If you ignore everything, then you'll also ignore assertions, contract
violations, null-reference exceptions and so on. The code will never be improved
if it never makes a noise. It will just stay silently crappy until someone
notices a subtle logical error somewhere and must painstakingly track it down to
your untested code.

You might say that production code shouldn't throw exceptions. This is true, but
we're explicitly not talking about production code here. We're talking about
code that has few to no tests and is acknowledged to be incomplete. If you move
code like this into production, then it's better to crash than to silently
corrupt data or impinge the user experience.

A crash will get attention and the code may even be fixed or improved. If you
write code that will crash on all but the "happy path" and it never crashes?
That's great. Do not program preemptively defensively in fresh code. If you have
established code that interfaces with other (possibly external) components and
you sometimes get errors that you can't work around in any other way, then it's
OK to catch and log those exceptions rather than propagating them. At least you
tried.

In the next article, we'll take a look at how all of these questions and
considerations can at all be reconciled with YAGNI. Spoiler alert: we think that
they can.

--------------------------------------------------------------------------------


[1] I recently read "Erlang and code style" by Jesper L. Andersen
    , which seems to have less to do with
    programming Erlang and much more to do with programming properly. The advice
    contained in it seems to be only for Erlang programmers, but the idea of
    strictly enforcing APIs between software components is neither new nor
    language-specific.