views:

284

answers:

4

I am mulling over the idea of writing a program to check for "leaky abstractions" in Java. The one area that popped into mind right away is with exceptions:

public class X
{
    // this one is fine, Readers throw IOExceptions so it is 
    // reasonable for the caller to handle it
    public void parse(final Reader r)
        throws IOException
    {
    }

    // This one is bad.  Nothing in the API indicate that JDBC 
    // is being used at all.
    public void process()
        throws SQLException
    {       
    }
}

Note, I do not want an argument on relative merits of checked/unchecked exceptions. What I am looking for is other examples (doesn't have to be exception handling) that people have that could also be reasonably caught via examining the source code or class files.

I am aware of checkstyle, findbugs, and PMD, and AFAIK none of them deal with this (and I am not opposed to putting the checks into one of those tools rather than write my own).

Are there any other examples of leaky abstractions that you come to mind that could be statically check for?

EDIT:

The reason why the second one is bad is that the method throws an exception where the client has no way of knowing that JDBC (for example, it could be anything) is being used. So the "leaky abstraction" is that JDBC is being used. If the underlying mechanism changed to soemthing else (say JPA which is a different database abstraction library) then the exceptions would all need to change as well. So the underlying database library is being leaked out.

A: 

You could consider extending your example to check for violations to the Liskov Substitution Principle. There are some who would say that a class implementing an interface should only throw the same exceptions as defined by the interface.

If you could analyze the usage of the object, it would be good to suggest that a more general type could be used (e.g., the method requires a SQLConnection, when an IDBConnection would do).

Paul Stovell
For Java the implementation cannot throw different types than interface (well it can throw subclasses of the exceptions declared). The ultimate idea is to have the tool suggest a better exception if one exists or at least let the program know they should make one if they want.
TofuBeer
I think I was thinking about synchronization then. I remember Java (which my knowledge of is limited and out of date, I will admit) allows you to mark a method as being thread synchronized - but that can differ between implementations of an interface?
Paul Stovell
Yes, synchronized and native cannot be specified in an interface as they are implementation details.
TofuBeer
+2  A: 

Since I didn't detect a question in the original post, I will ramble.

Any such tool would have to have the user tell it what exceptions qualify as non-leaks for a given class, and anything not on such a list would be a leak.

That's just exceptions. As I understand it, leaky abstractions apply to much more. Consider this:

class Client
{
    private Integer ID;

    public Integer ID() { return this.ID; }
}

Does this qualify as a leak? If later I needed to represent ID as Long, then it does. You might fix such a scenario like this:

class Client
{
    private ClientID ID;

    public ClientID ID() { return this.ID; }
}

class ClientID
{
    private Integer value;

    public ClientID(String id) { this.value = Integer.parseInt(id); }

    public String asString() { return value.toString(); }

    ... other methods that don't reveal value's type here ...
}

This would solve the leak out of Client, but now you'd run your tool on ClientID. Is it leaky?

This could get even more complicated if you have to examine methods that aren't simple getters. What if ClientID had a method that did some mathematical operation on its ID (indulge me) and returned an Integer. Is that a leak? Does this qualify as leaking that value is an Integer?

So I'm not sure if this is a code smell that a machine could easily catch.

moffdub
That is the sort of thing I am looking for, yes. I am sure there are many other examples of "leaks" that I cannot think of. Some might be practical to check for, others might not be. The exception one is relativly straight forward, I'm looking to see if there are some other low hanging fruit too.
TofuBeer
+1  A: 

So.

How to detect if an API leaks implementation details or does not keep the same level of abstraction.

You may probably watch the this talk. It explains how good APIs look like and are designed ( you can deduct from the good practices what are the bad practices )

For instance

Functionality should be easy to explain. If its a hard name it's generally a bad design.

From that you could discover, if the methods or return parameters give detail instructions they are not along with the level of abstraction.

For instance a high level method

 -initProcess(): SGN_MTL

May be leaking implemntation details on the return value.

The hard part here is to detect when the level of abstraction changes.

For instance in your method it would be ok to throw SQLExceptions all around if the code it self is the implementation of the JDCB layer.

Other source where you can see more of these topics is this list. http://www.jetbrains.com/idea/documentation/inspections.jsp

See the items under "Abstraction" ie.

  • Instance variable of concrete class: Report when the type of an instance variable is declared as a concrete class rather than an interface.

The classic example is:

 private ArrayList list;

when it will be better

 private List list;
OscarRyz
A: 

Thought of another one. Having a public member that deals with a non-public type.

class Foo { ... } // local to the package

public class Bar
{
    class Car() { ... }

    public Bar(Foo f) { ... }

    public Car star() { ... }
}

Bar and star would be leaky. You can see the Bar constructor b but can only call it from within the same package as the Bar class. You can call the star method but can only use the return value as an Object.

For both of these you would then see that you need to consider making the member either private or package access or making the type public.

TofuBeer
How about: public interface ICar {...} private class Car implements ICar {...} public ICar star() {...}
finnw