Sunday, March 6, 2011

Spring 2011 Reading List

I gave last Spring's reading list (which was more what I had read in the previous year) in this post. This spring, I am going to write what I am currently reading or hope to finish this Spring.
  • Seven Languages in Seven Weeks - this book looks interesting, and certainly the challenge of trying to meaningfully cover seven languages and language philosophies in a short book will be interesting if nothing else.
  • Programming in Scala - I have "played" with Scala, but not done more than toy programs. However, from everything I know about it- I believe I will really enjoy it. I buy into a lot of the functional vs object oriented dualism, and certainly enjoy the terse syntax. I think there are still interesting questions with regards to software development economics (i.e. how do we integrate Scala in a team of mixed skill levels, cost trade offs, etc.)
  • Thinking Forth - I'm not particularly interested in Forth, but I am interested in language design and problem solving philosophies.
  • Domain Driven Design - this was recommended by a commenter in the previous post, and I picked it up. I've made it through quite a bit of the book, and have enjoyed it so far. I had always heard this book referenced by Fowler et al, and really I should've read it years ago...
  • Introduction to Reliable Distributed Programming - its a Springer book, 'nuff said. I've been through a few distributed computing books, and have a fairly broad knowledge of the space. I am hoping to get a more mature, theoretically rigorous view of the problems now.
  • The Art of Multiprocessor Programming - this book is great. I enjoyed the nice mix of intuitive description and theoretical rigor. It's a nice blend of theory and pragmatics. I really recommend reading this for anyone that wants in depth knowledge of parallel computing.
  • Introductions to Neural Networks for Java - this was an interesting book to skim I don't really want to recommend it, because most of it is explaining his neural network code base instead of the underlying concepts. In any case, its a nice thing to skim over an afternoon.
  • Neural Networks and Learning Machines - Great textbook for all the theoretical background and mathematical proofs regarding Neural Nets and machine learning. I've had to crack open my calc books to freshen up while going through this...its dense.
  • Fuzzy Models and Genetic Algorithms for Data Mining and Exploration - I can't recommend this book (see my Amazon review if you're curious why), but it does provide a decent vocab overview for the topics it covers.

Well that's it for the moment! This spring is a bit more theory heavy than the previous list... that probably reflects my work and school change, which has been more research oriented in the last year. I'm not moving away from the real world just trying to get a more comprehensive grasp on both worlds in the areas which I am interested. I've always thought this was one of my strengths-- knowing enough theory to shape my thinking skills and be knowledgeable-- but continuously, deliberately enriching my implementation and practical skills to actually put the theory to good use! The intersection of the two is the more rewarding area for me, I think.

Steve

Saturday, March 5, 2011

Building Derby with Eclipse gotcha

This is partially a reference for myself and for anyone else trying to build Derby 10 from source using Eclipse. My thesis project involves modifying the on-disk page layout for a database (long story for another post). I am currently using Eclipse for my IDE. I just wanted to document a quick pain point in case anyone else is Googling for the answer:

I wanted to do a clean build of all of Derby. So I set up an ANT External Tool launch configuration:
  • Launch the build.xml from the root Derby directory
  • Working directory is the root Derby directory
  • Refresh resources upon completion
  • Uncheck the "build before launch
  • Choose the clobber, all targets (clobber is a total clean)
  • Defaults for everything else

I started to build and failed with the follow errors (I'm omitting some of the ones in the middle):

[javac] C:\DerbyDev\java\build\org\apache\derbyBuild\javadoc\DiskLayoutTaglet.java:24: package com.sun.tools.doclets does not exist
[javac] import com.sun.tools.doclets.Taglet;
[javac] ^
[javac] C:\DerbyDev\java\build\org\apache\derbyBuild\javadoc\DiskLayoutTaglet.java:25: package com.sun.javadoc does not exist
[javac] import com.sun.javadoc.*;
[javac] ^

...

[javac] C:\DerbyDev\java\build\org\apache\derbyBuild\javadoc\UpgradeTaglet.java:102: cannot find symbol
[javac] symbol : class Taglet
[javac] location: class org.apache.derbyBuild.javadoc.UpgradeTaglet
[javac] Taglet t = (Taglet) tagletMap.get(tag.getName());
[javac] ^
[javac] 40 errors

So as you can see from the first two errors -- its missing the dependency for Javadoc things. As it turns out Derby defines its own Taglet for Javadoc processing. All of these classes are defined in the JDKs tools.jar file, which is in the JDK's/lib directory (not included in the JRE).

Derby's build script includes tools.jar by:
<pathelement path="${java15compile.classpath};${java.home}/../lib/tools.jar"/>

Well I went to my command prompt and looked at my environment variable and java_home was set to the JDKs location. So this should've worked. I added a new launch configuration to execute the showenv target, and added an echo statement to include java.home.

I ran this and low and behold it was pointing to the JRE path, which doesn't have a tools.jar. So the world made sense now. In my eclipse installation, I have both the JDK and JRE installations defined in my preferences. For some reason (subconscious desire to shoot myself in the foot?), the JRE was chosen for this workspace, and thus the launch configuration used that.

So to resolve the problem, you can change the external tools launch configuration for the ANT script. Go to the JRE tab and select separate JRE and choose the JDK. Or change the default JRE for the workspace, and leave the launch configuration set to run in the same JRE as the workspace.

This isn't a tricky problem, but can be a little misleading...plus I haven't posted in a while ;-)

Wednesday, December 1, 2010

Defender Methods: Neal Gafter is *not* painting a bike shed

I follow the lambda-dev mailing list, and there is a discussion going on regarding defender methods and automatic disambiguation. Defender methods are a language feature that allows some interesting things:
  1. Adding methods to existing interfaces without breaking backwards compatability
  2. Multiple inheritance of behavior (but not state-- think interfaces with code)
The second item is profound (well both are), and it will be interesting to see how its usage is adopted by the community. The approach taken with defender methods is described as "declaration-site" extension methods, because it has a syntax present in the interface declaration. For example, imagine adding a "randomElement" method to java.util.Collection. Today, you can't do that without breaking every class that implements Collection. Using a defender method you can do:

interface Collection<T> {
T randomElement() default Collections.randomElement;
...
}

// add the default implementation to Collections
public class Collections {
...
public static <T> randomElement(Collection<T> c) {
...
}
}
Other languages implement use-site extension methods (e.g. C#) in which case the owner of Collections doesn't have change anything. You could write (in C#)

namespace SteveCode.Extensions {
static class MyCollections {
T randomElement(this ICollection<T> c) {
...
}
}
}
Then anywhere you state "using SteveCode.Extensions" (i.e. "import" the namespace) then you can invoke someCollection.randomElement() and the compiler will invoke the static method. Ergo, the "user" of the extension method has to indicate that he is using it.

There are some great articles out there discussing the pros/cons of declaration vs use site approaches, so I wont re-hash them. See Remi Forax's post and another.

Java is going with declaration-site style and one of the benefits as now inheritance can still work. I.e. if you augment an interface to add a new method with a "default" and then later in your concrete class you wish to specialize this new method, you can override it and consumers will call the specialized method via polymorphism (as expected). This doesn't work with use-site extension methods as they are just a compiler trick.

So on the lambda-dev list, there has been a discussion over a particular feature in the current specification. Take the following:

public class Impls {
public static void one(Object o) { ... }
public static void two(A a) { ... }
public static void two(B b) { ... }
}

interface A {
void execute() default Impls.one;
}
interface B {
void execute() default Impls.one;
}
If you defined a class MyAB implements A,B what would you expect to happen? Well currently the spec allows this "automatic disambiguation" because they both delegate to the same method/overload. Where this becomes a problem is if later the owner of B changes the default of B to something else. Now when you recompile MyAB suddenly it breaks and you must manually disambiguate by implementing execute inside MyAB.

Note that if the interfaces took a different overload of the default, for example:

interface A {
void execute() default Impls.two;
}
interface B {
void execute() default Impls.two;
}
Now class MyAB implements A,B would fail right off the bat the first time-- because there is no overload in common. Thus, automatic disambiguation would not work in this case either (nor would you want it). Lastly, imagine having:

public class Impls {
public static void one(Object o) { ... }
public static void two(Object o) { ... }
public static void two(A a) { ... }
public static void two(B b) { ... }
}
In this case, I think (and someone please correct me if I'm wrong) that normal overload rules will apply and this will compile, invoking two(Object o) in MyAB, but in another class that only extends A, it would call two(A a).

There is also some additional complexity that must be added somewhere (talk of changing the .class file format to include linking information) so that in the presence of changing versions of the interface, the code compiled against the old version will still work (remain binary compatible).

All in all, there is some complexity for doing this "automatic disambiguation". Neal Gafter of Java Puzzlers, Sun Java spec, etc. fame (not in that order) has raised the point of whether this complexity is worth it. One of his proposed solutions is just to use method bodies in the interface itself. This would remove the overload resolution of the "default" entirely, moving the disambiguation to the client at the very beginning instead of making it potentially a source incompatible change to modify the defender's default (probably unexpected). This seems like an entirely reasonable conversation to have on the mailing list as its a language detail that has the potential to have expensive impacts. The discussion was going on for a few days-- and Reinier Zwitserloot (of Project Lambok, etc. fame) chimes in with:

When is the currently proposed "default method is part of the signature" aspect going to result in a problem? If this is about ideological purity, then I vote we stop painting this bikeshed.

If you don't get the bikeshed reference, check here. In classic Neal fashion, he had a precise (ableit somewhat arrogant) retort, which upon reading I had a good chuckle:

An interesting reference to Parkinson's *law of triviality*. The basis of Parkinson's law is that people seem to care less about issues they don't understand, no matter how important those issues might be relative to the issues for which they express a preference. "painting this bikeshed" is an *ad hominem* suggestion that the participants in this discussion are really only commenting on those issues simple enough for them to understand.

In this case, however, you also appear to be saying that you don't fully appreciate the issue, and therefore don't see any point in continuing the discussion. Which ironically reinforces the reference to Parkinson's law.

I like Neal Gafter and Reinier Zwitserloot and am happy that everyone on the dev lists cares enough to take time to discuss these things for the (I believe) betterment of the Java community. Its not to say there should be unbounded discussion, but I personally found Neal's points useful. Automatic disambiguation does seem to require complexity that outweighs its benefit-- and that's worth discussing.

Steve

Friday, November 19, 2010

Notes on the Synthesis of Form

I just got in Notes on the Synthesis of Form by Christopher Alexander. This is an academic, theoretical treatise on design. It deals with "what is design", etc. It was written in 1962, but the ideas are obviously still relevant today--even though some of the motivating examples seem a little anachronistic. While it has nothing to do with software architecture (the term didn't even exist back then!), it is a fascinating read for those of us who enjoy some philosophical pandering every once in a while.

In the introduction "The Need for Rationality", the author makes clear his opinion of self-proclaimed "artists":


The modern designer relies more and more on his position as an "artist", on catchwords, personal idiom and intuition--for all of these relieve him of some of the burden of decision, and make his cognitive problems manageable. Driven on his own resources, unable to cope with the complicated information he is supposed to organize, he hides his incompetence in a frenzy of artistic individuality. As his capacity to invent clearly conceived, well-fitting forms is exhausted further, the emphasis on intuition and individuality only grows wilder.

In this atmosphere, the designer's greatest gift, his intuitive ability to organize physical form, is being reduced to nothing by the size of the tasks in front of him, and mocked by the efforts of "artists". What is worse, in an era that badly needs designers with the synthetic grasp of organization of the physical world, the real work has to be done by less gifted engineers, because the designers hide their gift in irresponsible pretension to genius.

(pg. 11)

I always enjoy well-written calls to humility. I wonder who in particular the author had in mind when he wrote this? What's funny is this was written in 1962 and seems no less valid in 2010 :-)

Steve

Tuesday, October 26, 2010

Deadlock two ways (Microsoft SQL Server)

Think of "Duck two-ways" or some other delicious meal. This is as tasty ;-)

If you have done any development with Microsoft SQL Server with even a moderate amount of concurrent processing, then you have probably witnessed the dreaded:
Transaction (Process ID 42) was deadlocked on lock resources with another process and has been chosen as the deadlock victim
This indicates that Microsoft SQL Server has detected a deadlock and has killed one of the processes to eliminate the stale mate. Let's go through two very different deadlock conditions that have the same central theme. I have found these to be extremely common in production.

Intuitively deadlock is a "deadly embrace" that occurs when two resources are being contended over. Thus, people are often surprised when they get deadlocks and "they weren't touching the same records!". Unfortunately, in traditional pessimistic locking solutions to concurrency control, other records must be locked to guarantee ACID properties. There are other solutions to concurrency control (MVCC is the one everyone reaches for) that avoid most/all of this. However, MSSQL by default employs a pessimistic approach. Also, I don't mean to sound as if I'm picking on MSSQL-- the principles below are true in a number of database implementations. DB/2 is also particularly nasty with pessimistic locking style deadlocks. The two presented below were reported to the JTDS forums, and are from real production.

Deadlock I


This is the common unicode "mismatch" problem. This has been covered on blogs a lot. I am going to use this as a way to describe how to research deadlocks. But if you're already bored, go ahead and skip to Deadlock II
Here is a table definition (slightly changed for brevity):

CREATE TABLE [PM1](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[KEYNAME] [varchar](50) NOT NULL,
[VALUE] [varchar](255) NULL,
CONSTRAINT [PK_PM1] PRIMARY KEY CLUSTERED
(
[ID] ASC
))
CREATE NONCLUSTERED INDEX [pmkv_idx] ON [PM1]
(
[KEYNAME] ASC,
)
Now we execute multiple, concurrent:
DELETE FROM PM1 WITH (ROWLOCK) WHERE KEYNAME = @P0

And we get a deadlock! Let's familiarize ourselves with some technical details and terms:

Clustered and Non-Clustered Indexes


There are two database structures for this DDL. One primary, clustered index, which has a key [ID]. And a secondary, non-clustered index, which has a key [KEYNAME]. A "clustered" index means that all of the data, the entire record, is stored in the leaf of the index. It is still an index--it is still searchable by [ID], but when it gets to a leaf, the entire data record is physically stored there. This is different from a secondary or non-clustered index in which the leaf of the index contains some kind of reference to the whole data record. In our secondary index, the index is "searchable" by [KEYNAME], but in the leaves of the index, the "reference" is actually the primary key value (ID), which can be used then to "search" the primary, clustered index. This is how databases use secondary indexes-- they find matching data with the index, then do a bookmark lookup, which is a join, into the clustered index to get the rest of the data.

Seeking vs Scanning


There are different actions the engine can perform on these structures: the engine can scan the index, in which case it will start somewhere (often the beginning) and start reading each record in order. As these indexes a B+-Trees, the leaf level is a doubly linked list. Thus, from a single leaf node, you can read sibling records in order without having to go "up and down" the index levels. Scanning is expensive as it is an O(n) operation which degrades as the number of records increases.

The engine can seek the index, in which case it will use a binary search to locate a leaf node (record). This is much preferable as it is a O(log n) operation-- and the base of that log is very high (the number of records per page). Thus seeking is why we put indexes on tables-- to take advantage of the "quick" searching.

Getting Information about your deadlock


In Microsoft SQL Server (2005+) there is a useful report that can give you good details about your deadlock. Without this information, it's very difficult to understand what's really happening. You can get this information two ways
  • SQL Server Profiler, capturing the Deadlock event
  • Enable Trace Flag 1222
I find it generally useful to enable T1222 as a startup parameter for SQL Server, so that whenever a deadlock occurs, it dumps the details to the SQL Server error log file. This is great, and incurs negligible performance overhead.

Trace 1222 Output


Let's look at the 1222 output and see what actually happened. There are two sections to the 1222: (1) the processes involved, (2) the wait-for graph, which shows which locks are acquired which are trying to be acquired. Here is an abbreviated 1222 for this deadlock:

<deadlock victim="process3dcab08">
 <process-list>
  <process id="process3dcab08" ...>
   <executionStack>
    <frame ... >DELETE FROM PM1 WITH (ROWLOCK) WHERE AND KEYNAME = @P0 </frame>
   </executionStack>
   <inputbuf>(@P0 nvarchar(4000))DELETE FROM PM1 WITH (ROWLOCK) WHERE KEYNAME = @P0 </inputbuf>
  </process>
  <process id="process3de1198" >
   ... (same as previous process) ...
  </process>
 </process-list>
 <resource-list>
  <keylock ... objectname="DB1.dbo.PM1" indexname="PM_IDidx" ... mode="X" ... >
   <owner-list>
    <owner id="process3de1198" mode="X"/>
   </owner-list>
   <waiter-list>
    <waiter id="process3dcab08" mode="U" requestType="wait"/>
   </waiter-list>
  </keylock>
  <keylock ... objectname="DB1.dbo.PM1" indexname="PM_IDidx" ... mode="X" ... >
   <owner-list>
    <owner id="process3dcab08" mode="X"/>
   </owner-list>
   <waiter-list>
    <waiter id="process3de1198" mode="U" requestType="wait"/>
   </waiter-list>
  </keylock>
 </resource-list>
</deadlock>

The process-list section describes all of the processes (concurrent executions) involved. I have colored one blue and one green to help distinguish them. So as we see each is holding an X (exclusive) lock, which the other is trying to get a U (update) lock for.

The database must hold an X lock before actually modifying a record, thus we expect this to be involved. However, a U lock is unexpected. Update locks are an "in between" lock, which are used by the database when scanning for potential records that need to be updated. In this case, a U lock is acquired before checking the value to see if it matches your WHERE clause. If it does, then the U is "upgraded" to an X lock, if not, then it is released. However, our WHERE claue has KEYNAME = @P0, which is indexed. Thus, we shouldn't be scanning anything-- we should be seeking on the index. When seeking-- the database goes exactly to the records which qualify, and thus X locks are requested to begin with. Thus, we expect to see only X locks from the statements above.

So what happened?


The answer is subtle: look at the input buffer for the delete statement:

<inputbuf>(@P0 nvarchar(4000))DELETE FROM PM1 WITH (ROWLOCK) WHERE KEYNAME = @P0 </inputbuf>

The parameter for the delete statement was declared by the driver as nvarchar, but if you look back at the DDL, you will see that the KEYNAME column is declared as varchar.

The NVARCHAR datatype is a unicode text datatype, whereas VARCHAR is non-unicode, 8-bit character datatype. The engine cannot directly compare an nvarchar value to a varchar value-- it must use a conversion. This conversion makes the where clause predicate not sargable, and that disallows the optimizer from seeking on it. Instead it scans the whole index, one record at a time, and for each one, requests a U lock, converts and checks the value, releases the U lock. Thus, for every delete of a single record, every record must be checked with an expensive, conflicting lock. And as seen in our case, two concurrent deletes happen to be checking and ran into each other.

The solution


So why did the driver do that? Well this was a Java application, and in Java strings are unicode by default. By default, the driver binds strings to their unicode counterpart. However, there is a setting on the driver's connection url, which instructs the driver to bind to non-unicode instead. The key is that you want to ensure that the driver is sending parameters the same as the underlying schema is defined. For JTDS the setting is sendStringParametersAsUnicode and it defaults to TRUE. Thus, if your schema is defined with all VARCHARs and you are using them in indexes, then you want to ensure this property is set to FALSE in your connection url. Here is an example of a connection url that forces the driver to send them as varchar instead of nvarchar:

jdbc:jtds:sqlserver://mssql01:1433/DB1;sendStringParametersAsUnicode=false

Deadlock II


This deadlock is a little less common, but interesting, because it also has an "unexpected" wait-for graph in the 1222 output. The schema was jBPM, which may be familiar. I need to see if someone has already fixed this in jBPMs delivered schema.

Trace 1222 Output


First, let's look at the 1222 trace:

<deadlock victim="process3de0868">
 <process-list>
  <process id="process3deb08" ...>
   ...
   <inputbuf>(@P0 bigint,@P1 int)delete from JBPM4_SWIMLANE where DBID_= @P0 and DBVERSION_= @P1</inputbuf>
  </process>
  <process id="process3de0868" ...>
   ... same as other process ...
  </process>
 </process-list>
 <resource-list>
  <keylock ... objectname="dbo.JBPM4_TASK" indexname="PK__JBPM4_TASK__2BFE89A6" mode="X" ...>
   <owner-list>
    <owner id="process3deb08" mode="X"/>
   </owner-list>
   <waiter-list>
    <waiter id="process3de0868" mode="S" requestType="wait"/>
   </waiter-list>
  </keylock>
  <keylock ... objectname="dbo.JBPM4_TASK" indexname="PK__JBPM4_TASK__2BFE89A6" mode="X" ...>
   <owner-list>
    <owner id="process3de0868" mode="X"/>
   </owner-list>
   <waiter-list>
    <waiter id="process3deb08" mode="S" requestType="wait"/>
   </waiter-list>
  </keylock>
 </resource-list>
</deadlock>

So we have two workers executing a delete by primary key... and they're deadlocking on S locks!? An S (shared) lock is used when the database needs to get a consistent read on something. Thus, by default, when you execute SELECT statements, shared locks are being issued under the covers to ensure read consistency (w.r.t the isolation level you're running at).

So what happened?


There is a hint in the wait-for graph that shows the problem: Look at the keylock that is being contended-- the indexname is PK__JBPM4_TASK__2BFE89A6. This is an auto-generated name for the primary, clustered index for the JBPM4_TASK table. We're deleting from the SWIMLANE table, so why would there be any locks on the TASK table? The TASK table has a foreign key reference (field swimlane_) to the SWIMLANE table. Thus, when you delete a SWIMLANE record, the database must ensure that there are no records referring to the SWIMLANE record. If there are, then the delete cannot proceed, because it would be a foreign key constraint violation. To do this check, the database needs to find records in the JBPM4_TASK table WHERE swimlane_ = id of swimlane being deleted. This read will be done using S locks to ensure consistency. Unfortunately, the JBPM4_TASK table does NOT have a secondary index on the swimlane_ column. Thus, the only option the database has is to scan the entire table by primary, clustered index. Thus, for each record, a S lock must be acquired, the record evaluated for the WHERE clause, and then the S lock is released. Each one of these is a deadlock opportunity if there are concurrent operations touching the TASK table.

The Solution


Adding an index on the swimlane_ column in the TASK table will reduce the n deadlock opportunities to 1, which is a significant improvement-- not to mention the performance improvement.

The Overall Theme


While these are very different deadlocks, the theme is the same: unintended scans. Scans are not only a performance problem, but a deadlock problem, as you increase the number of times that you request conflicting locks while holding locks. Thus, take care when considering which indexes to create-- its a tricky problem with many factors.

Steve

Monday, October 11, 2010

Some JIRA / GreenHopper Love

This will be another brief, non-technical, un-informative post to rant..but in a positive way! I actually will post something informative and useful one day ;-)

A team member and I have been using JIRA and recently started using the GreenHopper plug-in. The plug-in advertises that it is "agile" focused. I have always been extremely satisfied with Atlassian products, but this is something special. They really have done a great job in usability with GreenHopper.
  • The "boards" present a nice, intuitive, usable visualization of a part of the project.
  • Every aspect of the "boards" visualization (color coding, progress bars, in-place editing) reinforces with high affordance the functionality of that view.
  • The tools gets out of the way as much as possible in terms of navigation and functionality. Its intuitive. I find that I spend 95% of my time just on the Task board. I never need to leave.
  • Using the "boards" is fun!


For example, from the "task" board, I can open bugs, assign them to other people, begin progress, etc. This is my "specialized" view that I, a developer, use to see my tasks and at-a-glance get a view of the whole sprint.



Here are some noteworthy features of this visualization:
  • There are three columns for this sprint: To Do, In Progress, Done. You just drag a card between the columns to start progress or resolve as done. Just as you would with a card on a cork board (intuitive metaphor).
  • You can switch projects and sprints/versions by clicking the down arrows next in the headers
  • You can add bugs, features, etc. for this sprint just by clicking the button for that issue (right under the filter icon).
  • You can add comments to the issue simply or bring up a light box with the entire JIRA issue (see below).
  • There is a nice progress bar that shows the overall progress for the entire sprint.




When a new sprint comes around you can check out the "planning" board, where you can drag and drop cards to different releases or sprints. Also, there is a "chart" board which summarizes various charts (burndown rate, etc.), and a "released" board which provides metrics on completed sprints and releases.

This is just a little bit of why I like GreenHopper. I have just been so pleased with it that I wanted to share. I have used bugzilla, plain JIRA, and Team Foundation in the past. Team Foundation I know has much of the same (if not more) reporting than GreenHopper, but I never saw a visualization for me, the developer, that was as useful (defined by power/weight ratio) as what GreenHopper provides in the Planning Boards. Its the subtle simplicity and usability that I think makes GreenHopper so great-- not just sheer functionality.

If anyone actually reads this I would be interested to hear others' opinions about tools-- in particular other "killer features" like GreenHoppers planning boards.

Steve

Monday, September 20, 2010

Naming Matters

This title is kind of a two-for: "Naming matters" as in matters pertaining to naming and naming matters as gee golly-- names really do make a difference! So hurray for homonyms and making a neat opening to what is likely to be a boring post :-) (Have I hooked you to read further? dratz...)

API design "theory" is curious, because it seems to take a back seat in the academic literature, and yet the economic impact of bad API design is so great to the "problem" of software development. I guess the Software Engineering folk are fighting the good fight with code quality metrics, etc., but this is a hard topic, that I had never considered throughout my entire undergraduate career.

Here's an example of an API design mistake in my opinion (albeit a simple one). In Java, there are the following constants defined:
  • Integer.MIN_VALUE
  • Long.MIN_VALUE
  • etc.

These define the lowest value that can be represented by this datatype. Thus, for example, Integer.MIN_VALUE is -2147483648 given that its 4 bytes and uses a 2-s complement representation, etc.

These are used all over the place as sentinels. For example, a typical implementation of Dijkstra's shortest path algorithm initializes every node to a shortest distance of Integer.MAX_VALUE, and as the algorithm progresses, any value will (likely) be less than MAX_VALUE. Thus, it's a natural way to construct the algorithm without having to code a bunch of fragile "special cases" (adding to your cyclomatic complexity).

So what do you think Double.MIN_VALUE returns?

Well as a bug that I coded last week demonstrates, it does *not* return the minimum value of the range of values that can be represented in an instance of the Double data type. That would be ludicrous (this is me being sarcastic).

No, instead it returns the smallest positive value. You need to use Double.NEGATIVE_INFINITY for the smallest, *cough* minimum *cough* value.

Note that in C#/.NET Double.MinValue is the minimum value (i.e. negative number). I wonder what it is in limits.h...

So what's up Java?! I wonder how many man hours of time have been wasted on this pitfall since Java's inception? Can we ask Guy Steel and Bill Joy for a check? Just kidding...

Steve