Atom Feed SITE FEED   ADD TO GOOGLE READER

Always use jarjar to package implementation dependencies

jarjar is a sweet Java packaging tool that allows you to embed one .jar file in another. But rather than just smashing the jars together in one big archive, jarjar renames the embedded .jar's classes so that they live in the main jar's namespace. For example, Guice's ProxyFactory.java file has an impressive collection of imports:
package com.google.inject;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.internal.*;
import java.lang.reflect.*;
import java.util.*;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastConstructor;

class ProxyFactory implements ConstructionProxyFactory {
...
}

These imports include classes from net.sf.cglib and com.googe.common.collect. But when we package it up with jarjar, everything gets prefixed with the Guice package name: com/google/inject:
     0 Thu Jan 01 22:13:36 PST 2009 META-INF/
1726 Thu Jan 01 22:13:34 PST 2009 META-INF/MANIFEST.MF
11357 Sun May 25 20:34:04 PDT 2008 LICENSE
101 Sun May 25 20:34:04 PDT 2008 NOTICE
5975 Thu Jan 01 22:13:20 PST 2009 com/google/inject/AbstractModule.class
2466 Thu Jan 01 22:13:20 PST 2009 com/google/inject/Binder.class
806 Thu Jan 01 22:13:20 PST 2009 com/google/inject/Binding.class
414 Thu Jan 01 22:13:22 PST 2009 com/google/inject/BindingAnnotation.class
...
136 Sun May 25 20:34:04 PDT 2008 com/google/inject/internal/cglib/proxy/Callback.class
238 Sun May 25 20:34:04 PDT 2008 com/google/inject/internal/cglib/proxy/CallbackFilter.class
28315 Sun May 25 20:34:04 PDT 2008 com/google/inject/internal/cglib/proxy/Enhancer.class
5712 Sun May 25 20:34:04 PDT 2008 com/google/inject/internal/cglib/proxy/MethodProxy.class
5535 Sun May 25 20:34:04 PDT 2008 com/google/inject/internal/cglib/reflect/FastClass.class
1642 Sun May 25 20:34:04 PDT 2008 com/google/inject/internal/cglib/reflect/FastConstructor.class
...
8144 Sat Nov 29 14:11:22 PST 2008 com/google/inject/internal/collect/ImmutableList.class
9826 Sat Nov 29 14:11:26 PST 2008 com/google/inject/internal/collect/ImmutableMap.class
6026 Sat Nov 29 14:11:24 PST 2008 com/google/inject/internal/collect/Lists.class
12551 Sat Nov 29 14:11:26 PST 2008 com/google/inject/internal/collect/Maps.class

By using jarjar, Guice encapsulates these library dependencies. This is fantastic! Many problems are avoided by encapsulating the library dependency:
  • Guice users don't need to tell their classpath, IDE or build.xml files about cglib or Google Collections.

  • Other versions of the libraries won't conflict with the Guice version. We can ship one binary and support users of either extremely old or extremely new versions of cglib. Even if cglib broke compatibility every release (it doesn't), we aren't impacted.

  • We can freely change our own libraries. Should we add a dependency on paranamer in a future release, our users don't need to reconfigure their build scripts.

  • Most importantly, we can patch the libraries to fit our needs. If we want a special build of google-collections that includes our own hacks and tweaks, that's just fine. Our build doesn't even need to be compatible with the public version.


Standard jars for API dependencies


Guice's dependency on aopalliance doesn't use jarjar. Guice users implement the aopalliance interfaces, so the version independence and encapsulation offered by jarjar doesn't make much sense.

howmanyspacesafteraperiod.com

Inspired by the URL-as-question meme sites http://has the large hadron collider destroyed the world yet.com and http://should I use tables for layout.com, I present http://how many spaces after a period.com:

Audible on Android (and other devices)

I love my new G1, but it doesn't work with Audible yet. Fortunately, I have a workaround. This guide describes how to get audiobooks onto a G1. You'll need a Mac and $40 worth of software.

Download and install Tune4Mac. This app creates a virtual CD burner on your Mac, so when you tell iTunes to burn a CD, you don't actually need a blank CD-R. It's a clever hack, and the software works as promised. There's a Windows version, but I haven't tried it.

Register Tune4Mac. It's $39.95. You'll get the license key to your email immediately, paste that in the app's Registration dialog.


Configure AAC output in Tune4Mac. Tune4Mac automatically encodes audio to either MP3 or AAC. I tried MP3 encoding, and it was flaky, but AAC encoding worked just fine. Save this change by closing the dialog.


Add audiobooks to iTunes. Purchase DRM-protected books from Audible.com. If this is your first time using Audible, choose format #4, a high quality format that works with iTunes.


Create a playlist in iTunes. iTunes burns CDs using playlists, and we're going to be burning a virtual CD. I used a different playlist for each part of my two-part audiobook.


Prepare the fake burn. Click the 'Burn Disc' button in the bottom right corner of the iTunes app. Select Tune4Mac Virtual CDRW, Maximum Possible, Audio CD, none and Include CD Text. Click burn.


Burn fake CDs. Click 'Burn' on the dialog, and then Audio CDs on the dialog that follows. It took an hour for my computer to burn all the virtual CDs for an entire book. During this time, Tune4Mac automatically cycles fake blank CDs so you can go grab a coffee.


Use Finder to transfer the book to your device. I opened the Documents folder, and then the Tune4Mac folder to find my audiobook file. I plugged my device into my Mac, causing an icon for it to show up in the Finder. I dragged the .m4a file to this icon to transfer the book.


Eject the device. In the Finder, click the device's eject icon before detaching it. Otherwise the transfer might not finish properly.

Enjoy! You can now enjoy audiobooks on your device. Unfortunately, some devices don't bookmark audiobooks, so you'll need to remember your place when you stop listening.

Disclaimer: I admit that this process is painful and expensive. Hopefully Audible follows Amazon's lead and makes DRM-free audiobooks available for purchase. That way, Audible's huge library will be available to a larger audience.

Guice punches erasure in the face!

Java 5 Type Erasure is frustrating. I'm coding along, enjoying the glorious type safety of generics. And then I try to do a little more with the Ts, and erasure shows up, "You can't do that!" I can't test an object if it really is a T. I can't inspect T.class using reflection. And I certainly can't new up a T. Yuck.

We're in a unique situation with Guice. Its type literals and injection points have complete generic information. It can follow long chains of type variables throughout an object model. In my app, I've got a Robot<T> that injects a Claw<T>. When I request a Robot<Adamantium>, Guice knows to find an adamantium claw.

And now, this type information is available to application code. I can write a Claw<T> class that actually knows what T is at runtime!

Consider GuardingList<T>, which enforces type safety at runtime:
class GuardingList<T> extends AbstractList<T> {
private final TypeLiteral<T> elementType;
private final ArrayList<T> delegate = new ArrayList<T>();

@Inject
GuardingList(TypeLiteral<T> elementType) {
this.elementType = elementType;
}

public boolean add(T t) {
checkArgument(elementType.getRawType().isInstance(t),
"Cannot add %s which is not of type %s", t, elementType);
return delegate.add(t);
}

public T get(int i) {
return delegate.get(i);
}

public int size() {
return delegate.size();
}
}
When injected, Guice applies its type knowledge to inject the correct TypeLiteral<T>. The GuardingList blows up when I add an invalid element:
  public static void main(String[] args) {
Injector injector = Guice.createInjector();

List<String> listOfString = injector.getInstance(
new Key<GuardingList<String>>() {});
listOfString.add("A");
listOfString.add("B");

/* totally unsafe, but this compiles and runs */
List<Object> rawList = (List) listOfString;

/* throws an IllegalArgumentException, since the GuardingList
knows that its elements must be Strings */
rawList.add(666);
}
Types are the natural currency of Java. Erasure is frustrating 'cause it takes them away from you at runtime. And now Guice can give 'em back.

Reify today.

Just checked in: PrivateModules

Dependency injection is addictive. The pattern allows our applications to grow without harming developer productivity. But we have seen technical hurdles...

Sometimes our modules have competing dependencies. For example, we have a BillingModule and a PermissionsModule, each of which has a private database. If we configure two PersistenceModules to talk to the two databases, the bindings conflict. Guice doesn't allow duplicate bindings to PersistenceService or any other key.

A new Guice feature is intended to address this problem. Here's the class doc from PrivateModule:

A module whose configuration information is hidden from other modules. Only bindings that are explicitly exposed will be available to other modules and to the injector. Exposed keys must be explicitly bound, either directly or via another module that's installed by the private module.

In addition to the bindings configured via configurePrivateBindings(), bindings will be created for all methods with the @Provides annotation. These bindings will be hidden from other modules unless the methods also have the @Exposed annotation:

public class FooBarBazModule extends PrivateModule {
@Override protected void configurePrivateBindings() {
bind(Foo.class).to(RealFoo.class);
expose(Foo.class);

install(new TransactionalBarModule());
expose(Bar.class).annotatedWith(Transactional.class);

bind(SomeImplementationDetail.class);
install(new MoreImplementationDetailsModule());
}

@Provides @Exposed
public Baz provideBaz() {
return new SuperBaz();
}
}

Private modules inherit type converters, scopes, and interceptors from their containing modules. They can be nested within standard modules and within other private modules using install().

Private modules are implemented on top of parent injectors. Just-in-time bindings may be created in the parent injector, sharing them with all other modules. When bindings are shared:
  • Scoped instances are shared across modules. For example, if FooImpl is a shared singleton, the other modules get the same instance.
  • Bindings that inject the Injector get the parent injector. It will not be able to get injections bound in the private module.

Just-in-time bindings will not be shared if they have dependencies in the private module. To prevent it from being shared, write an explicit binding:
  bind(FooImpl.class);

This is a part of the ongoing work for Guice 2. Get the code from Guice SVN.

Rip.java: stream manipulation for Java programmers

I never learned sed or awk. Or even Perl. But I'm pretty good with Java's regex, and I'm familiar with the new text formatting facilities in Java 5.

So rather than tricking myself into learning sed and awk, I wrote my own stream processor that uses Java's regex and pattern syntax:
jessewilson$ Rip.java
Usage: Rip [flags] <regex> <format>

regex: a Java regular expression, with groups
http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html
you can (parenthesize) groups
\s whitespace
\S non-whitespace
\w word characters
\W non-word

format: a Java Formatter string
http://java.sun.com/javase/6/docs/api/java/util/Formatter.html
%[argument_index$][flags][width][.precision]conversion
'%s', '%1$s' - the full matched text
'%2$s' the first (parenthesized) group

Use 'single quotes' to prevent bash from interfering

flags:
--skip_unmatched: ignore input that doesn't match <regex>
-s:

--newline <text>: use <text> to separate lines in output
-n <text>:
So it takes Java regexes in, finds matching groups in parenthesis, and then spits those back out using String.format. Here's some examples:

jessewilson$ echo "7278 ttys001 0:00.66 ssh jessewilson.publicobject.com" |
Rip.java 'ssh.*' '%s'
ssh jessewilson.publicobject.com

jessewilson$ echo "http://publicobject.com/glazedlists/ Glazed Lists Homepage" |
Rip.java 'http://([\w.]+)\S*\s+(.*)' '%3$s: %2$s'
Glazed Lists Homepage: publicobject.com
These examples are certainly the tip-of-the-iceberg. I suspect I'll be using this tool to munge output from many processes into the input for many other processes.

Try Rip Out


Download Rip.java, make it executable (chmod a+x Rip.java) and put it somewhere on your path. In what is almost certainly more clever than useful, I hacked it up so the uncompiled source can be executed directly by Bash:
/*bin/mkdir /tmp/rip 2> /dev/null
javac -d /tmp/rip $0
java -cp /tmp/rip Rip "$@"
exit
*/
import java.io.*;
import java.util.*;
import java.util.regex.*;

public class Rip {
...
}

Replace my clever hack with a .class and wrapper script if you'd prefer.

Coding in the small with Google Collections: AbstractIterator

Part 17 in a Series.

I really like the Java Collections API. So much so, that I use 'em when I'm doing work that isn't particularly collectioney. For example, I recently wrote a quick-n-dirty app that rewrote some files line-by-line. Instead of using a Reader as input, I used an Iterator<String>. The easiest way to create such an iterator is to load the entire file into memory first.

Before:

  public Iterator<String> linesIterator(Reader reader) {
BufferedReader buffered = new BufferedReader(reader);
List<String> lines = new ArrayList<String>();

try {
for (String line; (line = buffered.readLine()) != null; ) {
lines.add(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}

return lines.iterator();
}
That code is simple, but inefficient. And it won't work if the file doesn't fit into memory. A better approach is to implement Iterator and to read through the file on-demand as the lines are requested. Google Collections ' AbstractIterator makes this easy. Whenever a new line is requested, it gets called back to read it from the stream.

After:

  public Iterator<String> linesIterator(Reader reader) {
final BufferedReader buffered = new BufferedReader(reader);

return new AbstractIterator<String>() {
protected String computeNext() {
try {
String line = buffered.readLine();
return line != null ? line : endOfData();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
}
This class is really takes the fuss out of custom iterators. Now it's not difficult to create iterators that compute a series, process a data stream, or even compose other iterators.

Coding in the small with Google Collections: Sets.union, intersection and difference

Part 16 in a Series.

The traditional approach to unions is to first create a new Set, and then to addAll using each component set. You can use a similar approach to do differences and intersections.

Before:

  private static final ImmutableSet<String> LEGAL_PARAMETERS;
static {
Set<String> tmp = new HashSet<String>();
tmp.addAll(REQUIRED_PARAMETERS);
tmp.addAll(OPTIONAL_PARAMETERS);
LEGAL_PARAMETERS = ImmutableSet.copyOf(tmp);
}

public void login(Map<String, String> params) {
if (!LEGAL_PARAMETERS.containsAll(params.keySet())) {
Set<String> unrecognized = new HashSet<String>(params.keySet());
unrecognized.removeAll(LEGAL_PARAMETERS);
throw new IllegalArgumentException("Unrecognized parameters: "
+ unrecognized);
}

if (!params.keySet().containsAll(REQUIRED_PARAMETERS)) {
Set<String> missing = new HashSet<String>(REQUIRED_PARAMETERS);
missing.removeAll(params.keySet());
throw new IllegalArgumentException("Missing parameters: " + missing);
}

...
}
Google Collections has methods that do set arithmetic in a single line.

After:

  private static final ImmutableSet<String> LEGAL_PARAMETERS
= Sets.union(REQUIRED_PARAMETERS, OPTIONAL_PARAMETERS).immutableCopy();

public void login(Map<String, String> requestParameters) {
if (!LEGAL_PARAMETERS.containsAll(requestParameters.keySet())) {
throw new IllegalArgumentException("Unrecognized parameters: "
+ Sets.difference(requestParameters.keySet(), LEGAL_PARAMETERS));
}

if (!requestParameters.keySet().containsAll(REQUIRED_PARAMETERS)) {
throw new IllegalArgumentException("Missing parameters: "
+ Sets.difference(REQUIRED_PARAMETERS, requestParameters.keySet()));
}

...
}
Unlike the traditional approach, these methods don't do any copies! Instead, they return views that delegate to the provided sets. In the occasional case when the copy is worthwhile, there's a handy method immutableCopy to give you one.

Google Collections talk, Aug 6 at the Googleplex

The Google Tech Users Group is hosting a talk that will interest Java developers:
Overview:
How the Google Collections Library builds on java.util to provide more building blocks for doing your job.

Where:
Building 42 of the Googleplex, Mountain View, California

When:
6:00pm Food, social, demos and announcements
7:00pm Talk by Kevin Bourrillion

If you'll be in the valley, you can register for the free event. After the talk, please join us for beers 'n' boardgames!

Correctness and my wife

I do this really annoying thing when I'm hanging out with my wife. I correct her when she uses the "wrong" words...

We're walking around town when we see something out of the ordinary - like a humongous fat dog or a friendly hobo or a police chase.
Her: "That was random"
Me: "It was unexpected. Nothing really random about it."
Her: "Ohhkayyy programmer boy. It was random. Get over it.

Our apartment is in a bit of a ghetto. So we're lying in bed and we can hear another loud argument between the neighbours downstairs. That couple has one of those dramatic relationships and they break out in yelling one night every month or so.
Me: Jodie, why are you so anxious? Get some sleep, gotta wake up early in the morning!
Her: "I can't sleep. I'm worried."
Me: "About what?"
Her: "Lots of abstract things. For example, I'm worried that she'll pull a gun on him, and they'll shoot a bullet through the ceiling, and it will hit me!"
Me: "Huh? That's not going to happen. And if it would, it would totally hit you in the ass and then you'd have a cool story."
Her: "But I'm a worrier. I always worry about abstract things like that."
Me: "That's not abstract. It's imaginary! You're worrying about something very explicit and vivid. An abstract worry might be about say, your happiness or the future. But not getting shot in the ass. That's just imaginary."
Her: "Ohhkayyy programmer boy. It was random. Get over it.

As you can see, I'm clearly getting all caught up in anal-retentive word usage and it drives Jodie crazy. I think it's cause I read code all day and argue with my coworkers whether the method should be named safeSubstring() or lenientSubstring(). She was surprised to hear that as somebody who uses computer languages all day, I spend a lot of time thinking about words from English.

I gotta stop code-reviewing our conversations.

Two use cases - two names?

Today I did something I've never done before - I created a method that's an 'alias' to another method. I still think this is the right thing to do, but I still find it kind of weird...

Getting Providers


Binder.getProvider() allows your module to get a Provider<T> while the injector is still being created. The returned provider can't be used until the injector is ready, but that usually isn't a deal breaker:
public class ListOfFiltersModule extends AbstractModule {
public void configure() {
/* get the filters set, perhaps bound with multibindings */
final Provider<Set<Filter>> filterSetProvider
= getProvider(new TypeLiteral<List<Filter>>(){});

/* use that provider in a provider instance binding */
bind(new TypeLiteral<List<Filter>>() {}).toProvider(
new Provider<List<Filter>>() {
public List<Filter> get() {
Set<Filter> filtersUnordered = filterSetProvider.get();
List<Filter> result = new ArrayList<Filter>();
result.addAll(filtersUnordered);
Collections.sort(filteresUnordered, FILTER_ORDER);
return Collections.unmodifiableList(result);
});
}
}

For this purpose, this works pretty good.

Listing Dependencies


There's another use case for getProvider - making dependencies explicit. It's handy to include the list of bindings that a module wants right in the module. This helps Guice to fail-faster if a required binding is missing. More importantly, it makes the dependency obvious to the maintainers of the code:
public class DeLoreanModule extends Module {
public void configure() {
/* bindings needed from other modules */
getProvider(FluxCapacitor.class);
getProvider(TimeCircuits.class);

/* our bindings */
bind(Car.class).toProvider(DmcCarProvider.class);
}

This is okay, but a clarifying comment is necessary here. Otherwise the getProvider call looks out of place - the maintainers you're trying to help may delete this "unnecessary" call!

requireBinding


Giving getProvider a better name for use case #2 makes the intentions more explicit. It also means we can axe the now-redundant comment:
public class DeLoreanModule extends Module {
public void configure() {
requireBinding(FluxCapacitor.class);
requireBinding(TimeCircuits.class);

bind(Car.class).toProvider(DmcCarProvider.class);
}

Behind-the-scenes, all requireBinding() does is call through to getProvider() to ensure that key is bound. But this is just an implementation detail - we could later change requireBinding to do something different if there was a preferred alternative.

Multiple Names


We have two use cases and two names. But only one implementation! I think it's legit, but it's a bit surprising. Where else does this come up?

Don't create multiple annotations with the same simple name

Nobody reads imports. Good IDEs do their best to pretend imports don't even exist - they'll hide 'em from you, and manage them for you. They'll even add imports on demand when you're writing new code.

Suppose you create your own, say @Inject or @RequestScoped annotation. In the code, it's practically impossible to differentiate between this and a Guice-supplied annotation:
package com.publicobject.pizza;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.publicobject.pizza.annotation.RequestScoped;
import com.publicobject.pizza.annotation.Inject;
import com.publicobject.pizza.geography.GeographyService;
import com.publicobject.pizza.hr.EmployeeRoster;

@RequestScoped
public class PizzaStore {
@Inject PizzaStore(GeographyService geography,
EmployeeRoster workers) { ... }
}

Your head will explode debugging problems if the wrong annotation is applied. Guice can detect some problems (blowing up on a mismatched scope annotation) but it's still risky business.

TypeResolver tells you what List.get() returns

It's diminishingly rare that I get to write code that improves the internals of both Glazed Lists and Guice...

Glazed Lists' BeanProperty


BeanProperty is a convenient utility class that can expose a JavaBeans getter/setter property as its own object. You give it a class (like Baz.class) and a property name (like "bar") and it gives you a full property object - you can use it to read and write the property. It even exposes the property's type:
class Baz {
private String bar;

String getBar() {
return bar;
}
void setBar(T bar) {
this.bar = bar;
}
}

public void testBaz() {
Baz baz = new Baz();
BeanProperty<Baz> bar = new BeanProperty<Baz>(Baz.class, "bar");
assertEquals(String.class, bar.getValueClass());
bar.set(baz, "hello");
assertEquals("hello", baz.getBar());
}

Eric Burke reported a bug where BeanProperty wasn't doing the right thing for generic properties. In this example, we report the value type of Foo.bar as Object.class rather than String.class:
class Foo<T> {
T getBar();
}

class Baz extends Foo<String> {}

In order to get the return type of Baz.getBar(), we need to map Foo's type parameter T to java.lang.String.

Guice's ProviderMethods


The upcoming release of Guice lets you specify bindings with annotated methods:
class BarProviderMethods {
@Provides @Singleton
Bar provideBar() {
Bar result = new Bar();
result.setBaz("hello");
return result;
}
}

...but we run into problems if users specify generic provider methods. Guice wants to bind the return type of the provider method. Unfortunately, due to generics, that type might be insufficient:
class SetProviderMethods<T> {
@Provides
Set<T> provideSetOfT(T onlyElement) {
return ImmutableSet.of(onlyElement);
}
}

Enter TypeResolver


This class takes a generic type (like ArrayList<String>) and exposes precisely what the return types will be:
  public void testTypeResolver() {
Type listOfString = new TypeLiteral<List<String>>() {}.getType();
Method getMethod = List.class.getMethod("get", int.class);

TypeResolver resolver = new TypeResolver(listOfString);
assertEquals(String.class, resolver.getReturnType(getMethod));
}

I suspect this class is generally useful for any app that uses a reasonable amount of reflection. If this is useful to you, grab the source from Guice SVN.

The reasons I'm not on iPhone

It's really tempting. As far as devices go, iPhone 3G is the best there is. It's a generation ahead of its competitors, and the gap is growing. The app store is great for both developers and for its users. But I'm not gonna get one:

Amazon MP3. Lots of good songs with no 'deregister this computer' nonsense. But Amazon MP3 cannot compete with iTunes on Apple's platform—third party apps can't download while I surf the web. This also kills movie and video downloads.

Skype. Voice-over-IP is forbidden on the iPhone 'cause AT&T wants to bill voice traffic at a different rate than data. Even on my home network, AT&T would prefer I pay them for each minute of each call. I want net neutrality but the iPhone won't have any of it.

Flash and Java. That means no Line Rider, Hulu, Naval Command or flexgames.com. Apple would prefer for developers to write iPhone-only apps rather than phone-independent apps. This is good for Apple but bad for developers.

The iPhone is a lock-in platform. Sorry, Apple, but I'm not ready for that level of commitment.

Strict vs. Forgiving APIs

Suppose it's the early 1990's and you're James Gosling implementing String.substring(int, int) for the first time. What should happen when the index arguments are out-of-range? Should these tests pass? Or throw?
  public void testSubstring() {
assertEquals("class", "superclass".substring(5, 32));
assertEquals("super", "superclass".substring(-2, 5));
assertEquals("", "superclass".substring(20, 24));
assertEquals("superclass", "superclass".substring(10, 0));
}


Forgiving APIs


In a forgiving API, these tests pass. The implementation would recognize the out-of-range indices and correct for them. Benefits of forgiving APIs:
  • Fault-tolerant. An off-by-one mistake won't bring a production system to its knees.
  • Easier to code against. If you don't know what to use for a given argument, just pass null and the implementation will do something reasonable.


Strict APIs


In a strict APIs, the out-of-range arguments to substring are forbidden and the method throws an IllegalArgumentException. Benefits of strict APIs:
  • Fail-fast. An off-by-one mistake will be caught in unit tests, if they exist.
  • Easier to maintain. By limiting the number of valid inputs, there's less behaviour to maintain and test.
  • More Predictable. Mapping invalid inputs to behaviour is an artform. In the example, should substring(10, 0) return the empty string? Or "superclass"? What would the caller expect?


For maintainability, I almost always prefer strict APIs. I like to think of the classes in my code as the gears in a fine Swiss watch. Everything fits together tightly, with firm constraints on both the inputs and the outputs. I can refactor with confidence because the system simply won't work if I've introduced problems into it. With a forgiving API, I could introduce bugs and not find out about them until much later.