<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
 <title>Kent R. Spillner's Blog - Work</title>
 
 <link href="http://kent.spillner.org/blog/work/" />
 <updated>2012-04-09T15:18:31-07:00</updated>
 <id>http://kent.spillner.org/blog/work/</id>
 <author>
   <name>Kent R. Spillner</name>
   <email>kspillner@acm.org</email>
 </author>
 
 <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/kent-spillner-work" /><feedburner:info uri="kent-spillner-work" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry>
   <title>Make All Environments Consistent</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/8TpYUJ92GTM/make-all-environments.html" />
   <updated>2009-12-30T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/work/2009/12/30/make-all-environments</id>
   <content type="html">&lt;p&gt;Consistent environments are important on software projects.  They increase the team&amp;#8217;s productivity, make it easier to diagnose, replicate and resolve bugs, make it easier to tune performance and scale the application, and reduce the cost of administration and maintenance.  Building consistent environments is a great candidate for automation; in fact, tools like &lt;a href="http://wiki.opscode.com/display/chef/Home"&gt;Chef&lt;/a&gt; and &lt;a href="http://reductivelabs.com/products/puppet/"&gt;Puppet&lt;/a&gt; were written for exactly this purpose.  But Chef and Puppet are complicated, heavyweight solutions.  They also require a considerable amount of infrastructure themselves (&lt;a href="http://www.git-scm.com"&gt;git&lt;/a&gt;, &lt;a href="http://www.ruby-lang.org"&gt;Ruby&lt;/a&gt;, etc.)!&lt;/p&gt;
&lt;p&gt;For lighter, simpler configuration needs I prefer &lt;a href="http://www.opengroup.org/onlinepubs/009695399/utilities/make.html"&gt;Makefiles&lt;/a&gt;.  Makefiles are great for this sort of thing because versions of &lt;a href="http://www.opengroup.org/onlinepubs/009695399/utilities/make.html"&gt;Make&lt;/a&gt; exist for every operating system, and most &lt;span class="caps"&gt;UNIX&lt;/span&gt; systems come with some version already installed.  I use Make to install my personal configuration settings for common utilities on each machine I use (see &lt;a href="http://github.com/sl4mmy/dotfiles"&gt;http://github.com/sl4mmy/dotfiles&lt;/a&gt;), for example.&lt;/p&gt;
&lt;p&gt;Makefile syntax is familiar to system administrators and easy to learn, and because Makefiles are &lt;a href="http://www.opengroup.org/onlinepubs/009695399/utilities/sh.html"&gt;shell scripts&lt;/a&gt; (with a few format constraints) they map directly to the sequence of steps you would perform manually to setup your environments.  But arguably the biggest advantage of Makefiles is they won&amp;#8217;t do anything when the file about to be installed or modified exists.  This is critical in shared production environments where services might already be installed and configured for other applications, and you don&amp;#8217;t want to risk accidentally overwriting something important.&lt;/p&gt;
&lt;p&gt;Consider a project dependent on &lt;a href="http://java.sun.com"&gt;Java&lt;/a&gt; and &lt;a href="http://jruby.org"&gt;JRuby&lt;/a&gt;, and a convention that third-party applications should be installed under &lt;code&gt;/opt/apps/&amp;lt;name&amp;gt;/&amp;lt;name&amp;gt;-&amp;lt;version&amp;gt;&lt;/code&gt;.  The following Makefile installs and configures this project&amp;#8217;s environment accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# Makefile

JAVA_VERSION=1.6.0_18
JRUBY_VERSION=1.4.0

# TARGET 0:
all: java jruby

# TARGET 1:
java: /opt/apps/java/java-${JAVA_VERSION}/

# TARGET 2:
jruby: /opt/apps/jruby/jruby-${JRUBY_VERSION}/

# TARGET 3:
/opt/apps/java/java-${JAVA_VERSION}/: /opt/apps/java/ /tmp/java-${JAVA_VERSION}.tar.gz
	(cd /opt/apps/java; tar czf /tmp/java-${JAVA_VERSION}.tar.gz)

# TARGET 4:
/opt/apps/jruby/jruby-${JRUBY_VERSION}/: /opt/apps/jruby/ /tmp/jruby-${JRUBY_VERSION}.tar.gz
	(cd /opt/apps/jruby; tar czf /tmp/jruby-${JRUBY_VERSION}.tar.gz)

# TARGET 5:
/opt/apps/java/: /opt/apps/
	mkdir /opt/apps/java

# TARGET 6:
/opt/apps/jruby/: /opt/apps/
	mkdir /opt/apps/jruby

# TARGET 7:
/tmp/java-${JAVA_VERSION}.tar.gz:
	# Shell commands to download an archive of Java and save it as /tmp/java-${JAVA_VERSION}.tar.gz

# TARGET 8:
/tmp/jruby-${JRUBY_VERSION}.tar.gz:
	# Shell commands to download an archive of JRuby and save it as /tmp/jruby-${JRUBY_VERSION}.tar.gz

# TARGET 9:
/opt/apps/:
	mkdir -p /opt/apps

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br/&gt;
Starting from the top:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# Makefile

JAVA_VERSION=1.6.0_18
JRUBY_VERSION=1.4.0

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should always name your Makefiles as &lt;code&gt;Makefile&lt;/code&gt; because Make will look for that file in the current directory by default when it runs.  You &lt;em&gt;are&lt;/em&gt; free to name your Makefiles anything you wish, but if they are &lt;em&gt;not&lt;/em&gt; named &lt;code&gt;Makefile&lt;/code&gt; then Make will require additional configuration telling it which file to load.&lt;/p&gt;
&lt;p&gt;Makefile comments start with #, just like shell scripts.  The &lt;code&gt;# Makefile&lt;/code&gt; comment in this example is not necessary, it is only for illustrative purposes.&lt;/p&gt;
&lt;p&gt;The first two non-comment lines define variables that will be used throughout this Makefile.  You may safely assume the syntax for defining and referencing variables in Makefiles is identical to shell scripting.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# TARGET 0:
all: java jruby

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Target 0, named &lt;code&gt;all&lt;/code&gt; here but could be named anything, is the default target because it is the first target declared in this Makefile. Running &lt;code&gt;make&lt;/code&gt; in the directory containing this Makefile is equivalent to running &lt;code&gt;make all&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make is a file-centric build tool; it assumes the purpose of each target is to produce (or &amp;#8220;make,&amp;#8221; natch) a single file.  By convention, the name of a target is the name of the output file produced by invoking that target.  In our example, Make assumes that the &lt;code&gt;all&lt;/code&gt; target will produce a file in the current directory named &lt;code&gt;all&lt;/code&gt;.  Make uses this convention to determine whether or not it actually needs to execute that target: if a file named &lt;code&gt;all&lt;/code&gt; already exists in the current directory, Make won&amp;#8217;t execute the &lt;code&gt;all&lt;/code&gt; target.&lt;/p&gt;
&lt;p&gt;Of course, targets are &lt;em&gt;not required&lt;/em&gt; to actually produce a file with the same name as the target, but if they don&amp;#8217;t Make will never think such targets are up-to-date and will execute them every time.  In this case, we want Make to do exactly that; our goal is not to produce a file named &lt;code&gt;all&lt;/code&gt; in the current directory, our goal is to set up a fully working environment on any machine by simply copying this Makefile and running &lt;code&gt;make&lt;/code&gt;.  We are using &lt;code&gt;all&lt;/code&gt; as a convenience target so that we can make &lt;em&gt;both&lt;/em&gt; &lt;code&gt;java&lt;/code&gt; and &lt;code&gt;jruby&lt;/code&gt; by default.  If there were more dependencies, we could create separate targets for each of them and add them as additional prerequisites of &lt;code&gt;all&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;all&lt;/code&gt; target is declared with two prerequisites: &lt;code&gt;java&lt;/code&gt; and &lt;code&gt;jruby&lt;/code&gt;.  Make executes all prerequisites before executing the target, so our &lt;code&gt;java&lt;/code&gt; and &lt;code&gt;jruby&lt;/code&gt; targets will execute before &lt;code&gt;all&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# TARGET 1:
java: /opt/apps/java/java-${JAVA_VERSION}/

# TARGET 2:
jruby: /opt/apps/jruby/jruby-${JRUBY_VERSION}/

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;java&lt;/code&gt; and &lt;code&gt;jruby&lt;/code&gt; targets are declared in Target 1 and Target 2, and each have a single prerequisite of the Java and JRuby installation directories defined in our convention.  Absolute paths are valid target names in Makefiles, and since they are used as prerequisites here, Make will search for targets with those names before it executes the &lt;code&gt;java&lt;/code&gt; or &lt;code&gt;jruby&lt;/code&gt; targets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# TARGET 3:
/opt/apps/java/java-${JAVA_VERSION}/: /opt/apps/java/ /tmp/java-${JAVA_VERSION}.tar.gz
	(cd /opt/apps/java; tar czf /tmp/java-${JAVA_VERSION}.tar.gz)

# TARGET 4:
/opt/apps/jruby/jruby-${JRUBY_VERSION}/: /opt/apps/jruby/ /tmp/jruby-${JRUBY_VERSION}.tar.gz
	(cd /opt/apps/jruby; tar czf /tmp/jruby-${JRUBY_VERSION}.tar.gz)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt; and &lt;code&gt;/opt/apps/jruby/jruby-${JRUBY_VERSION}/&lt;/code&gt; targets are declared in Target 3 and Target 4.  They both have two prerequisites, and one step.  Notice that the trailing slash in each target&amp;#8217;s name makes it explicit to Make that these targets produce directories.&lt;/p&gt;
&lt;p&gt;Steps are shell commands Make runs when it executes a target, and virtually any valid shell script is also a valid step.  Steps are associated with the target immediately above them in the Makefile, and they &lt;em&gt;must&lt;/em&gt; begin with a &lt;code&gt;TAB&lt;/code&gt; (&lt;strong&gt;no spaces!&lt;/strong&gt;).  Multiple steps can be associated with a target and will run in order, but each line &lt;em&gt;must&lt;/em&gt; begin with a &lt;code&gt;TAB&lt;/code&gt; (&lt;strong&gt;no spaces!&lt;/strong&gt;).  Makefiles are very touchy about whitespace: every line beginning with a &lt;code&gt;TAB&lt;/code&gt; below a target definition is a step for that target, and the first line that does &lt;em&gt;not&lt;/em&gt; begin with a &lt;code&gt;TAB&lt;/code&gt; is the separator between targets.&lt;/p&gt;
&lt;p&gt;When Target 3 is executed, Make will spawn a temporary subshell, change the current working directory to &lt;code&gt;/opt/apps/java&lt;/code&gt; and expand &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt; there.  The parenthesis around the step definition are not required by Make, they are used here as in a regular shell script: the enclosed commands will run in a subshell, returning to the parent shell when finished.  I did this so that changing directories in this step won&amp;#8217;t affect the rest of the Makefile.  Target 4 does the same for JRuby.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# TARGET 5:
/opt/apps/java/: /opt/apps/
	mkdir /opt/apps/java

# TARGET 6:
/opt/apps/jruby/: /opt/apps/
	mkdir /opt/apps/jruby

...

# TARGET 9:
/opt/apps/:
	mkdir -p /opt/apps

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Target 3 (&lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt;) depends on &lt;code&gt;/opt/apps/java/&lt;/code&gt;, declared as Target 5, and Target 4 (&lt;code&gt;/opt/apps/jruby/jruby-${JRUBY_VERSION}/&lt;/code&gt;) depends on &lt;code&gt;/opt/apps/jruby/&lt;/code&gt;, declared as Target 6.  Both targets depend on &lt;code&gt;/opt/apps/&lt;/code&gt;, declared as Target 9.  These three targets ensure that the correct directory structure exists according to our convention, creating missing directories as necessary.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# TARGET 7:
/tmp/java-${JAVA_VERSION}.tar.gz:
	# Shell commands to download an archive of Java and save it as /tmp/java-${JAVA_VERSION}.tar.gz

# TARGET 8:
/tmp/jruby-${JRUBY_VERSION}.tar.gz:
	# Shell commands to download an archive of JRuby and save it as /tmp/jruby-${JRUBY_VERSION}.tar.gz

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Targets 7 and 8 are placeholders showing how to download the necessary files before building an environment.&lt;/p&gt;
&lt;p&gt;So, what happens when you run &lt;code&gt;make&lt;/code&gt; on a clean environment without any of the directory structure or tarballs necessary for this project?  Follow along by tracing the prerequisites in the Makefile.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;all&lt;/code&gt; is the default target, Make starts by looking for a file named &lt;code&gt;all&lt;/code&gt; in the current directory; unable to find that file, Make tries to execute the prerequisites of &lt;code&gt;all&lt;/code&gt;, &lt;code&gt;java&lt;/code&gt; and &lt;code&gt;jruby&lt;/code&gt;.  Since no file named &lt;code&gt;java&lt;/code&gt; exists in the current directory either, Make looks up the target named &lt;code&gt;java&lt;/code&gt; and then tries to execute its prerequisite, &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt;.  Since that directory does not exist, Make looks up the target named &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt; and tries to execute its prerequisites, &lt;code&gt;/opt/apps/java/&lt;/code&gt; and &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt;.  Since the directory &lt;code&gt;/opt/apps/java/&lt;/code&gt; does not exist, Make looks up the target named &lt;code&gt;/opt/apps/java/&lt;/code&gt; and tries to execute its prerequisite, &lt;code&gt;/opt/apps/&lt;/code&gt;.  Since that directory does not exist, Make looks up the target named &lt;code&gt;/opt/apps/&lt;/code&gt;, sees that it has no prerequisites, and executes the steps associated with that target (&lt;code&gt;mkdir -p /opt/apps&lt;/code&gt;) which produces the directory &lt;code&gt;/opt/apps/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make then backtracks to the &lt;code&gt;/opt/apps/java/&lt;/code&gt; target, determines that its prerequisites were previously executed, and executes its steps (&lt;code&gt;mkdir /opt/apps/java&lt;/code&gt;) which produces the directory &lt;code&gt;/opt/apps/java/&lt;/code&gt;.  Make backtracks again to the &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt; target, sees that it still has one unsatisfied prerequisite, and looks up the target named &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt;.  Make sees that &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt; has no prerequisites, and executes its steps to download an archive of Java and save it as &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt;.  Make backtracks to the &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt; target again, sees that all of its prerequisites are now satisfied, and executes its steps which produce the directory &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Make then backtracks all the way back to the &lt;code&gt;all&lt;/code&gt; target, and does the same for its other prerequisite, &lt;code&gt;jruby&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, consider what happens when you re-run &lt;code&gt;make&lt;/code&gt; in the directory containing this Makefile (or run it for the first time in an environment which was previously setup manually).  Make sees that &lt;code&gt;all&lt;/code&gt; is the default target, but cannot find a file named &lt;code&gt;all&lt;/code&gt; in the current directory, so it tries to execute its prerequisites, &lt;code&gt;java&lt;/code&gt; and &lt;code&gt;jruby&lt;/code&gt;.  There is no file named &lt;code&gt;java&lt;/code&gt; in the current directory, either, but the &lt;code&gt;java&lt;/code&gt; target&amp;#8217;s only prerequisite is &lt;code&gt;/opt/apps/java/java-${JAVA_VERSION}/&lt;/code&gt;, and that directory &lt;em&gt;does&lt;/em&gt; exist, so Make skips the prerequisite&amp;#8217;s steps and goes straight to executing the steps associated with the &lt;code&gt;java&lt;/code&gt; target.  Since there are none, nothing happens, and Make backtracks to the &lt;code&gt;all&lt;/code&gt; target and tries to execute its other prerequisite, &lt;code&gt;jruby&lt;/code&gt;.  Similarly, there is no file named &lt;code&gt;jruby&lt;/code&gt;, but the directory corresponding to the &lt;code&gt;jruby&lt;/code&gt; target&amp;#8217;s only prerequisite &lt;em&gt;does&lt;/em&gt; exist, so Make skips executing the prerequisite target, going straight to the steps associated with &lt;code&gt;jruby&lt;/code&gt;.  Again, there are none so nothing happens, Make backtracks back to the &lt;code&gt;all&lt;/code&gt; target, determines its prerequisites were previously executed and so tries to execute the steps associated with &lt;code&gt;all&lt;/code&gt;.  Since there are no steps associated with &lt;code&gt;all&lt;/code&gt;, nothing happens, and Make finishes successfully without having done &lt;em&gt;anything&lt;/em&gt;.  Perfect for production environments!&lt;/p&gt;
&lt;p&gt;What happens if you delete &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt; and re-run &lt;code&gt;make&lt;/code&gt; in the directory containing the Makefile?  Well, nothing.  Make never sees that file is missing since the directory &lt;code&gt;/opt/apps/java-${JAVA_VERSION}&lt;/code&gt; exists.  But if you delete the directory &lt;code&gt;/opt/apps/java-${JAVA_VERSION}/&lt;/code&gt; and re-run &lt;code&gt;make&lt;/code&gt;, then &lt;code&gt;/tmp/java-${JAVA_VERSION}.tar.gz&lt;/code&gt; will be downloaded again and &lt;code&gt;/opt/apps/java-${JAVA_VERSION}/&lt;/code&gt; will be re-created.  The point is: you must carefully ensure all of your application&amp;#8217;s dependencies are properly exposed as prerequisites in your Makefile, not hidden as nested prerequisites of prerequisites.&lt;/p&gt;
&lt;p&gt;I love &lt;a href="http://www.opengroup.org/onlinepubs/009695399/utilities/make.html"&gt;Make&lt;/a&gt; as a low-touch solution for setting up consistent environments!  It&amp;#8217;s simple to learn and easy to use, and it is low-risk for shared production environments because it reduces the likelihood of modifying or deleting files used by other applications.  And when you outgrow Make&amp;#8217;s capabilities and need a more powerful tool, &lt;a href="http://www.opengroup.org/onlinepubs/009695399/utilities/make.html"&gt;Makefiles&lt;/a&gt; are the perfect way to consistently install and configure &lt;a href="http://wiki.opscode.com/display/chef/Home"&gt;Chef&lt;/a&gt; or &lt;a href="http://reductivelabs.com/products/puppet/"&gt;Puppet&lt;/a&gt; across multiple machines.&lt;/p&gt;









&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/8TpYUJ92GTM" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/work/2009/12/30/make-all-environments.html</feedburner:origLink></entry>
 
 <entry>
   <title>Java Build Tools: Ant vs. Maven</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/zb-lPsAO0Cg/java-build-tools.html" />
   <updated>2009-11-14T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/work/2009/11/14/java-build-tools</id>
   <content type="html">&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;span class="caps"&gt;TRANSLATIONS&lt;/span&gt;:&lt;/strong&gt; &lt;a href="http://prietopa.wordpress.com/2010/01/29/herramientas-de-construccion-de-java-ant-vs-maven/"&gt;Spanish&lt;/a&gt; (gracias, Jos&amp;eacute;!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;span class="caps"&gt;UPDATED&lt;/span&gt; 2010-01-06:&lt;/strong&gt; linked to demonstration of the &amp;#8220;10 minute mvn clean build&amp;#8221; problem, and added notes about: slow build times, excessive memory use, bad test result output, untrusted repository artifacts, and external configuration files.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;span class="caps"&gt;UPDATED&lt;/span&gt; 2010-02-21:&lt;/strong&gt; linked to Spanish translation (courtesy of Jos&amp;eacute; Manuel Prieto)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The best build tool is the one you write yourself.  Every project&amp;#8217;s build process is unique, and often individual projects need to be built multiple different ways.  It is impossible for tool authors to anticipate every build&amp;#8217;s requirements, and foolhardy to try (&lt;a href="http://jakarta.apache.org"&gt;Apache&lt;/a&gt; developers: take note).  The best any tool can do is provide a flexible library of reusable tasks that can easily be adapted to your needs, but even that is insufficient.  Off-the-shelf tasks never suit your project perfectly.  You will waste countless hours struggling to make those tasks do &lt;em&gt;exactly&lt;/em&gt; what you need, only to give up and write a plugin instead.  Writing your own custom build tool is quick and easy, and requires less maintenance than you fear.  Don&amp;#8217;t be afraid: builds should fit your project, not the other way around.&lt;/p&gt;
&lt;p&gt;If you don&amp;#8217;t want to write your own build tool, then you should use &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;.  &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt; is the best existing build tool for Java projects.  &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt; provides a bunch of standard methods to perform common build tasks, and anything else can be quickly implemented in &lt;a href="http://www.ruby-lang.org"&gt;Ruby&lt;/a&gt;.  Writing build scripts in a real programming language gives &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt; a huge advantage over other tools.  There are other advantages, too, but none are as important.&lt;/p&gt;
&lt;p&gt;So, you should write custom build tools for your projects.  If you don&amp;#8217;t want to, then you should switch to &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;.  If you can&amp;#8217;t switch, you should lobby for the right to switch.  If politics drives technology decisions, if you will never be allowed to switch, then quit your job or leave the project.&lt;/p&gt;
&lt;p&gt;If you lack the courage to quit, then use &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt;.  &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; is the second best existing build tool for Java projects.  Although inferior to &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;, &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; is still a great build tool.  &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; is mature and stable, it is fast, and it comes with a rich library of tasks.  &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; makes it possible (but &lt;a href="http://alex-verkhovsky.blogspot.com/2009/03/lets-use-real-languages-for-builds.html"&gt;not at all easy&lt;/a&gt;) to script rich, complex builds processes custom-tailored to your project.&lt;/p&gt;
&lt;p&gt;So, write your own build tool, or else switch to &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;, or fight to switch to &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;, or quit and go some place where you can use &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;.  And if all else fails, use &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; until you can find a new job somewhere else that uses &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s it!  Those are the &lt;strong&gt;only&lt;/strong&gt; choices I can recommend!  Because you never, &lt;strong&gt;ever&lt;/strong&gt;, under &lt;em&gt;any&lt;/em&gt; circumstances want to use &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; builds are an infinite cycle of despair that will slowly drag you into the deepest, darkest pits of hell (where &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; itself was forged).  You will initially only spend ten minutes getting &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; up and running, and might even be happy with it for a while.  But as your project evolves, and your build configuration grows, the basic &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-pom.html"&gt;pom.xml&lt;/a&gt; that you started with will prove inadequate.  You will slowly add more configuration to get things working the way you need, but there&amp;#8217;s only so much you can configure in &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt;.  Soon, you will encounter &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; low glass ceiling for the first time.  By &amp;#8220;encounter,&amp;#8221; I mean &amp;#8220;smash your head painfully against.&amp;#8221;  By &amp;#8220;for the first time,&amp;#8221; I mean &amp;#8220;you will do this repeatedly and often in the future.&amp;#8221;  Eventually, you&amp;#8217;ll figure out some convulted &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-pom.html"&gt;pom.xml&lt;/a&gt; hackery to work around your immediate issue.  You might even be happy with &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; again for a while&amp;#8230; until another limitation rears its ugly little head.  It&amp;#8217;s a lot like some tragic Greek myth, only you are the damned soul and the eternity of suffering is your build process.&lt;/p&gt;
&lt;p&gt;Seriously.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is a &lt;em&gt;horrible&lt;/em&gt; implementation of &lt;em&gt;bad&lt;/em&gt; ideas.  I believe someone, somewhere had (perhaps still has) a vision for &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; that was sensible, if not seductive.  But the actual implementation of &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; lacks any trace of such vision.  In fact, everything in &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is so bad that it serves as a valuable example of how &lt;em&gt;not&lt;/em&gt; to build software.  You know your build is awesome when it works the opposite of &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Consider the test results output from &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; &lt;a href="http://maven.apache.org/plugins/maven-surefire-plugin/"&gt;Surefire plugin&lt;/a&gt;.  Everything seems fine as long as all of your tests are passing, but &lt;a href="http://maven.apache.org/plugins/maven-surefire-plugin/"&gt;Surefire&lt;/a&gt; reports are a &lt;em&gt;nightmare&lt;/em&gt; to debug when things go wrong!  The only information logged to the console is the name of the failing test class.  You must &lt;em&gt;manually&lt;/em&gt; cross-reference that name with a log file written in the &lt;code&gt;target/surefire-reports/&lt;/code&gt; directory, but those logs are written &lt;em&gt;one per test class&lt;/em&gt;!  So, if multiple test classes fail, you must separately check multiple log files.  It seems like a minor thing, but it quickly adds up to a major annoyance and productivity sink.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; advocates claim their tool embraces the principle of &lt;em&gt;&lt;a href="http://www.sonatype.com/books/maven-book/reference/installation-sect-conventionConfiguration.html"&gt;Convention Over Configuration&lt;/a&gt;&lt;/em&gt;; &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; advocates are liars.  The only convention &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; supports is: &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html"&gt;compile, run unit tests, package .jar file&lt;/a&gt;.  Getting &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; to do &lt;em&gt;anything&lt;/em&gt; else requires  &lt;em&gt;configuring&lt;/em&gt; the conventions.  Want to package a .war file?  &lt;a href="http://maven.apache.org/plugins/maven-war-plugin/index.html"&gt;Configure it&lt;/a&gt;.  Want to run your application from the command line?  &lt;a href="http://mojo.codehaus.org/exec-maven-plugin/java-mojo.html"&gt;Configure it&lt;/a&gt;.  Want to run acceptance tests or functional tests or performance tests with your build, too?   You can &lt;a href="http://docs.codehaus.org/display/MAVENUSER/Maven+and+Integration+Testing"&gt;configure it&lt;/a&gt;, but it involves &lt;strong&gt;not&lt;/strong&gt; running your unit tests, or not running them during the &lt;em&gt;conventional&lt;/em&gt; unit test phase of your build process, or&amp;#8230;  Want to generate code coverage metrics for your project?  You can &lt;a href="http://mojo.codehaus.org/cobertura-maven-plugin/index.html"&gt;configure that&lt;/a&gt;, too, but your tests will run &lt;em&gt;twice&lt;/em&gt; (or only once, but not during the &lt;em&gt;conventional&lt;/em&gt; unit test phase), and sometimes it reports 0% code coverage despite the comprehensive test suite.&lt;/p&gt;
&lt;p&gt;Speaking of configuration, &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; has the worst configuration syntax since &lt;a href="http://www.sendmail.org"&gt;Sendmail&lt;/a&gt;: &lt;a href="http://www.ltg.ed.ac.uk/~ht/normalForms.html"&gt;alternating normal form&lt;/a&gt; &lt;a href="http://www.xml.com"&gt;&lt;span class="caps"&gt;XML&lt;/span&gt;&lt;/a&gt;.  As a consequence, &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; configuration is verbose, difficult to read and difficult to write.  Things you can do in one or two lines of &lt;a href="http://www.ruby-lang.org"&gt;Ruby&lt;/a&gt; or &lt;a href="http://www.xml.com"&gt;&lt;span class="caps"&gt;XML&lt;/span&gt;&lt;/a&gt; with &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt; or &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; require six, seven, eight lines of &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-pom.html"&gt;pom.xml&lt;/a&gt; configuration (assuming it&amp;#8217;s even &lt;em&gt;possible&lt;/em&gt; with &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s nothing consistent about &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; configuration, either.  Some things are configured as classpath references to .properties files bundled in .jar files configured as dependencies, some things are configured as absolute or relative paths to files on disk, and some things are configured as system properties in the &lt;span class="caps"&gt;JVM&lt;/span&gt; running &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt;.  And some of those absolute paths are portable across projects because &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; knows how to correctly resolve them, but some are not.  And sometimes &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is smart enough to recursively build projects in the correct order, but sometimes it&amp;#8217;s not.&lt;/p&gt;
&lt;p&gt;And some things aren&amp;#8217;t even configured in the &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-pom.html"&gt;pom&lt;/a&gt;!  Some things, like &lt;a href="http://maven.apache.org/repository-management.html"&gt;Maven repositories&lt;/a&gt;, servers, and authentication credentials, are configured in &lt;a href="http://maven.apache.org/settings.html"&gt;settings.xml&lt;/a&gt;.  It is perfectly reasonable to want to keep user&amp;#8217;s passwords out of &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-pom.html"&gt;pom.xml&lt;/a&gt; files which will be checked into the project&amp;#8217;s version control repository.  But &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; solution is terrible: all this configuration goes in a &lt;a href="http://maven.apache.org/settings.html"&gt;settings.xml&lt;/a&gt; file that lives outside of any project&amp;#8217;s directory.  You can&amp;#8217;t directly share any of this configuration between your desktop and laptop, or with other developers, or with your project&amp;#8217;s build servers.  But it &lt;em&gt;is&lt;/em&gt; &lt;strong&gt;automatically&lt;/strong&gt; shared with &lt;em&gt;every&lt;/em&gt; single &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; project you work with, and potentially every single &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; project &lt;em&gt;every user&lt;/em&gt; on that machine works with.  When a new developer joins your project, they must &lt;em&gt;manually&lt;/em&gt; merge the necessary configuration into their existing &lt;a href="http://maven.apache.org/settings.html"&gt;settings.xml&lt;/a&gt;.  When a new agent is added to your build server farm, the necessary configuration is &lt;em&gt;manually&lt;/em&gt; merged into its existing &lt;a href="http://maven.apache.org/settings.html"&gt;settings.xml&lt;/a&gt;.  Ditto for when you migrate to a new machine.  And when &lt;em&gt;any&lt;/em&gt; of this configuration needs to be updated, it must be &lt;em&gt;manually&lt;/em&gt; updated on &lt;em&gt;every&lt;/em&gt; single machine!  This was a solved problem before &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; came along, too: properties files.  Project teams can put generic configuration like this in a properties file which is checked in to version control, and individual developers can override that information in local properties file which are not checked in to version control.&lt;/p&gt;
&lt;p&gt;All this stuff in &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; &amp;#8212; the conventions, the configuration, the process &amp;#8212; is governed by &amp;#8220;The Maven Way&amp;#8221;.  Unfortunately, &amp;#8220;The Maven Way&amp;#8221; is undocumented.  You can catch fleeting glimpses of it by trawling the &lt;a href="http://maven.apache.org"&gt;Maven documentation&lt;/a&gt;, searching the &lt;a href="http://www.google.com"&gt;Google&lt;/a&gt;, or buying books written by &lt;a href="http://maven.apache.org/team-list.html"&gt;Maven developers&lt;/a&gt;.  The other way you encounter &amp;#8220;The Maven Way&amp;#8221; is by tripping over (or smashing against) its invisible boundaries.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; was not built to be flexible, and it does &lt;em&gt;not&lt;/em&gt; support every possible build process.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; was built for &lt;a href="http://jakarta.apache.org"&gt;Apache&lt;/a&gt; projects, and assumes every project&amp;#8217;s build process mirrors &lt;a href="http://jakarta.apache.org"&gt;Apache&amp;#8217;s&lt;/a&gt; own.  That&amp;#8217;s great news for open-source library developers who volunteer on their own time and to whom &amp;#8220;release&amp;#8221; means &amp;#8220;upload a new .zip file to your website for others to manually find, download, and add to their own projects.&amp;#8221;  It sucks for everyone else.  While &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt; and &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; can accommodate every build process, &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; can&amp;#8217;t; it is possible, and in fact quite likely, that &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; just doesn&amp;#8217;t support the way you want to build your software.&lt;/p&gt;
&lt;p&gt;And &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; dependency management is completely, entirely, irrevocably broken.  Actually, I take that back; &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; strategy of  downloading &lt;a href="http://ibiblio.org"&gt;ibiblio&lt;/a&gt; to the user&amp;#8217;s home directory and then dumping everything on the classpath is incredibly stupid and wrong and should never be confused with &amp;#8220;dependency management.&amp;#8221;  I recently worked on a &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; project which produced a 51 MB .war file; by switching to &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; with hand-rolled dependency management, we shrunk that .war file down to 17 MB.  Hrmmm&amp;#8230; 51 &amp;#8211; 17 = 34 = 17 &amp;#215; 2, or: 2/3 of the original bulk was useless crap &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; dumped on us.&lt;/p&gt;
&lt;p&gt;Extraneous dependencies don&amp;#8217;t just eat up disk space, they eat up precious &lt;span class="caps"&gt;RAM&lt;/span&gt;, too!  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is an all-around memory hog.  Relatively simple projects, with only a parent &lt;a href="http://maven.apache.org/guides/introduction/introduction-to-the-pom.html"&gt;pom&lt;/a&gt; and a few sub-modules, require extensive &lt;span class="caps"&gt;JVM&lt;/span&gt; memory tuning with all those fancy &lt;code&gt;JAVA_OPTS&lt;/code&gt; settings you typically only see on production servers.  Things are even worse if your &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; build is integrated with your &lt;span class="caps"&gt;IDE&lt;/span&gt;.  It&amp;#8217;s common to set your JVM&amp;#8217;s max heap size to several hundred megabytes, the max permgen size to a few hundred megabytes, and enable permgen sweeping so classes themselves are garbage collected.  And all this just to build your project, or work with &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; in your &lt;span class="caps"&gt;IDE&lt;/span&gt;!&lt;/p&gt;
&lt;p&gt;Funny story: on that same project I once endured a ten minute &amp;#8220;mvn clean&amp;#8221; build because &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; thought it needed yet more crap in order to &amp;#8220;rm -rf ./target/&amp;#8221; (see a similar example: &lt;a href="http://gist.github.com/267553"&gt;http://gist.github.com/267553&lt;/a&gt;).  Actually, there&amp;#8217;s nothing funny about that story; trust me: you don&amp;#8217;t want a build tool which automatically downloads unresolved dependencies before cleaning out your build output directories.  You don&amp;#8217;t want a build tool which automatically downloads unresolved dependencies, &lt;strong&gt;&lt;span class="caps"&gt;PERIOD&lt;/span&gt;&lt;/strong&gt;!  Automatically downloading unresolved dependencies makes your build process &lt;em&gt;nondeterministic&lt;/em&gt;!  Good ol&amp;#8217; nondeterminism: loads of fun in school, not so fun at work!&lt;/p&gt;
&lt;p&gt;And all that unnecessary, unwanted network chatter takes time.  You pay a performance penalty for &lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; broken dependency management on every build.  Ten minute clean builds are horrible, but adding an extra minute to &lt;em&gt;every&lt;/em&gt; build is even worse!  I estimate the average additional overhead of &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is about one minute per build, based on the fact that the one time I switched from &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; to &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; the average build time dropped from two and a half minutes to one and a half.  Similarly, the one time I switched from &lt;a href="http://ant.apache.org"&gt;Ant&lt;/a&gt; to &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; the average build time increased from two minutes to three.&lt;/p&gt;
&lt;p&gt;You have no control over, and limited visibility into, the dependencies specified by your dependencies.  Builds &lt;em&gt;will&lt;/em&gt; break because different copies of &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; &lt;em&gt;will&lt;/em&gt; download different artifacts at different times; your local build &lt;em&gt;will&lt;/em&gt; break again in the future when the dependencies of your dependencies accidentally release new, non-backwards compatible changes without remembering to bump their version number.  Those are just the innocent failures, too; the far more likely scenario is your project depends on a specific version of some other project which in turn depends on the &lt;span class="caps"&gt;LATEST&lt;/span&gt; version of some other project, so you still get hosed even when downstream providers &lt;em&gt;do&lt;/em&gt; remember to bump versions!  Every release of every dependency&amp;#8217;s dependencies becomes a new opportunity to waste several hours tracking down strange build failures.&lt;/p&gt;
&lt;p&gt;But &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is even worse than that: not only does &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; automatically resolve your project&amp;#8217;s dependencies, it automatically resolves its own plugins&amp;#8217; dependencies, too!  So now not only do you have to worry about separate instances of &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; accidentally downloading incompatible artifacts (or the same instance downloading incompatible artifacts at different times), you also have to worry about your build tool itself behaving differently across different machines at different times!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://maven.apache.org"&gt;Maven&amp;#8217;s&lt;/a&gt; broken dependency management is also a gaping security hole, since it is currently impossible in &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; to determine where artifacts originally came from and whether or not they were tampered with.  Artifacts are automatically checksummed when they are uploaded to a repository, and &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; automatically verifies that checksum when it downloads the artifact, but &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; implicitly trusts the checksum on the repository it downloaded the artifact from.  The current extent of &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; artifact security is that the &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; developers control who has write access to the authoritative repository at &lt;a href="http://www.ibiblio.org/maven/"&gt;ibiblio&lt;/a&gt;.  But there is no way of knowing if the repository you download all your dependencies from was poisoned, there is no way of knowing if your local repository cache was poisoned, and there is no way of knowing which repository artifacts in your local repository cache came from or who uploaded them there.&lt;/p&gt;
&lt;p&gt;These problems are not caused by careless developers, and are not solved by using &lt;a href="http://maven.apache.org/repository-management.html"&gt;repository managers&lt;/a&gt; to lock down every artifact &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; needs.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong if it assumes humans never make mistakes.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong if it requires users to explicitly specify every version of every dependency, and every dependency&amp;#8217;s dependencies, to reduce the likelihood of downloading incompatible artifacts.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong if it requires a third-party tool to prevent it connecting to the big, bad internets and automatically downloading random crap.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong if it thinks nothing of slowing down &lt;em&gt;every&lt;/em&gt; build by connecting to the network and checking &lt;em&gt;every&lt;/em&gt; dependency for &lt;em&gt;any&lt;/em&gt; updates, and automatically downloading them.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong if it behaves differently on my laptop at the office and at home.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong if it requires an internet connection to delete a directory.  &lt;a href="http://maven.apache.org"&gt;Maven&lt;/a&gt; is broken and wrong.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://rake.rubyforge.org"&gt;Save&lt;/a&gt; &lt;a href="http://ant.apache.org"&gt;yourself&lt;/a&gt;.&lt;/p&gt;










&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/zb-lPsAO0Cg" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/work/2009/11/14/java-build-tools.html</feedburner:origLink></entry>
 
 <entry>
   <title>And Now for Something Completely Different</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/VZ8yV3RMhs0/something-different.html" />
   <updated>2009-11-13T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/life/work/2009/11/13/something-different</id>
   <content type="html">&lt;p&gt;Today is my last day working for &lt;a href="http://www.thoughtworks.com"&gt;ThoughtWorks&lt;/a&gt;.  After four and a half years, I decided to hang up my consultant&amp;#8217;s cape and try something new: I accepted an offer from &lt;a href="http://www.drw.com"&gt;&lt;span class="caps"&gt;DRW&lt;/span&gt; Trading Group&lt;/a&gt; starting next week.&lt;/p&gt;
&lt;p&gt;I am extremely excited about my new job at &lt;a href="http://www.drw.com"&gt;&lt;span class="caps"&gt;DRW&lt;/span&gt;&lt;/a&gt;, but the decision to leave &lt;a href="http://www.thoughtworks.com"&gt;ThoughtWorks&lt;/a&gt; was difficult and I am sad to say &amp;#8220;goodbye!&amp;#8221;  &lt;a href="http://www.thoughtworks.com"&gt;ThoughtWorks&lt;/a&gt; was an important part of my life these past several years, and I am leaving behind a lot of great friends.  I will miss working with &lt;a href="http://blogs.thoughtworks.com"&gt;ThoughtWorkers&lt;/a&gt; every day, and I wish them all the very best.&lt;/p&gt;
&lt;p&gt;Ultimately, my decision to leave stems from my frustration with consulting.  Ostensibly, companies hire consultants for their expertise in helping solve difficult problems.  Typically, companies select consultants based on past experience and expertise in successfully solving similar problems.  But always companies ignore or push back against anything running counter to their current way of doing things, no matter how much they paid for the suggestion.&lt;/p&gt;
&lt;p&gt;I accept that compromise, education and negotiation are important aspects of consulting, and I agree it is unreasonable &amp;#8211; and would be unwise &amp;#8211; to expect clients to do everything exactly as they are advised.  I also enjoy helping others solve problems.  I am just tired of arguing about the same things with different clients over and over again.  And hence the change.&lt;/p&gt;
&lt;p&gt;I am not bitter or jaded, I bear no grudge; it is just time for me to try something new away from client politics.  &lt;a href="http://www.thoughtworks.com"&gt;ThoughtWorks&lt;/a&gt; is a great company, and I can&amp;#8217;t wait to see what things lie in store for them in the future!  But I won&amp;#8217;t miss my cape.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/VZ8yV3RMhs0" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/life/work/2009/11/13/something-different.html</feedburner:origLink></entry>
 
 <entry>
   <title>Private Methods are a Code Smell</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/bClapgKoyiU/private-methods-stink.html" />
   <updated>2009-11-12T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/work/2009/11/12/private-methods-stink</id>
   <content type="html">&lt;p&gt;Private methods are a code smell.  They should be moved to collaborating classes and made public.&lt;/p&gt;
&lt;p&gt;Private helper methods indicate classes are doing too many things.  Moving private helper methods to different classes, including creating new classes if necessary, splits the original responsibilities across multiple classes leading to simpler, better designs.&lt;/p&gt;
&lt;p&gt;Sometimes, private methods are created to split complex logic or processes into small, easily digested pieces.  Often, such private methods have gnarly dependencies because they directly access or modify internal state.  Moving these methods to appropriate collaborators (again, creating new classes as necessary) exposes such dependencies.  Eliminating these dependencies simplifies the new &lt;span class="caps"&gt;API&lt;/span&gt;, which improves readability and understanding.&lt;/p&gt;
&lt;p&gt;Sometimes, private methods are created just to give pieces of functionality more descriptive names.  Although descriptive names are desirable, creating private methods to provide descriptive names for things is still a smell.  Moving these methods to collaborators and making them public creates opportunities for future reuse without reducing the clarity of the original code.&lt;/p&gt;
&lt;p&gt;Taking small steps to improve design leads to flashes of brilliant design inspiration.  As code slowly evolves into better shape, bits and pieces fall into place until another, superior design becomes clear.  Making private methods public and moving them to (perhaps missing) collaborators is a simple and effective way to quickly improve design.  The resulting code is simpler, more testable, more reusable, more cohesive, and less coupled.  And when a superior design suddenly presents itself, a few public methods on many classes are easier to refactor than a few classes with many private methods.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/bClapgKoyiU" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/work/2009/11/12/private-methods-stink.html</feedburner:origLink></entry>
 
 <entry>
   <title>The Tao of Test-Driven Development</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/BYa9E8SbGLI/tao-of-test-driven-development.html" />
   <updated>2009-11-11T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/work/2009/11/11/tao-of-test-driven-development</id>
   <content type="html">&lt;p&gt;I gave my &lt;em&gt;Tao of Test-Driven Development&lt;/em&gt; tutorial at &lt;span class="caps"&gt;OOPSLA&lt;/span&gt; 2009 in Orlando, FL.  The tutorial began with a quick lecture-style introduction to test-driven development, and concluded with a hands-on coding dojo for attendees to practice what they learned.&lt;/p&gt;
&lt;p&gt;The slides from my presentation are now &lt;a href="http://github.com/sl4mmy/presentations/tree/master/tao-of-test-driven-development/"&gt;available online&lt;/a&gt; in Keynote, PowerPoint and &lt;span class="caps"&gt;PDF&lt;/span&gt; formats.&lt;/p&gt;
&lt;p&gt;I also uploaded &lt;a href="http://github.com/sl4mmy/presentations/tree/master/tao-of-test-driven-development/"&gt;starter code for parts 1 and 2 of the dojo&lt;/a&gt;.  Although the dojo was technology-agnostic and attendees free to use any language, the starter code uses Java, JUnit and Ant.&lt;/p&gt;
&lt;p&gt;The starter code for part 2 includes my solution to part 1, but there are no right or wrong solutions.  My solution is illustrative of &lt;em&gt;my&lt;/em&gt; approach to solving part 1, but is by no means the only solution.  Indeed, I practiced part 1 three times in preparation for the tutorial and ended up with a different solution each time.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/BYa9E8SbGLI" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/work/2009/11/11/tao-of-test-driven-development.html</feedburner:origLink></entry>
 
 <entry>
   <title>Introducing Hippie</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/tczSspnKjLs/introducing-hippie.html" />
   <updated>2009-11-10T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/work/2009/11/10/introducing-hippie</id>
   <content type="html">&lt;p&gt;My current project integrates with a lot of external services.  Unfortunately, our service-level agreements do not extend to our development and testing environments, so our application breaks frequently because of service problems.  Services go down, services have bugs in the latest development versions deployed in our environments, our environments are upgraded to new versions of services that are not backwards compatible with the old versions, etc.&lt;/p&gt;
&lt;p&gt;We write &lt;a href="http://www.junit.org"&gt;JUnit&lt;/a&gt; tests to verify each service is running correctly in our environments, so we do not waste time debugging problems caused by service failures.  These tests make my team more productive, but unfortunately they do nothing to make the services more reliable.  At least, not by themselves&amp;#8230;&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="http://github.com/sl4mmy/hippie"&gt;hippie&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://github.com/sl4mmy/hippie"&gt;Hippie&lt;/a&gt; is an open source tool that automatically sends &lt;a href="http://www.nagios.org"&gt;Nagios&lt;/a&gt; &lt;a href="http://nagios.sourceforge.net/docs/3_0/passivechecks.html"&gt;passive checks&lt;/a&gt; based on the result of running &lt;a href="http://www.junit.org"&gt;JUnit&lt;/a&gt; tests.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://github.com/sl4mmy/hippie"&gt;hippie&lt;/a&gt; bridges the divide between developer tools and system administrator tools.  &lt;a href="http://www.junit.org"&gt;JUnit&lt;/a&gt; tests automatically notify &lt;a href="http://www.nagios.org"&gt;Nagios&lt;/a&gt; which automatically notifies other teams that their services are broken.&lt;/p&gt;
&lt;p&gt;It is an extension of the automatic build notifications our continuous integration tools provide, only at much, much finer resolution.  &lt;a href="http://github.com/sl4mmy/hippie"&gt;Hippie&lt;/a&gt; makes it possible to notify Alice on &amp;#8220;Team: Tiger&amp;#8221; when TigerTests#shouldRespondWithOkStatus() fails, but notify Bob in production support when LegacyTests#shouldAlwaysBeUpAndRunning() fails.&lt;/p&gt;
&lt;p&gt;Those &lt;a href="http://www.junit.org"&gt;JUnit&lt;/a&gt; tests we wrote no longer just prevent us from wasting time debugging service problems, they now kick off a chain of events culminating (hopefully) with someone else fixing the problem.  And &lt;em&gt;that&lt;/em&gt; is the ultimate goal of every programming endeavor.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/tczSspnKjLs" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/work/2009/11/10/introducing-hippie.html</feedburner:origLink></entry>
 
 <entry>
   <title>GitHub Pages</title>
   <link href="http://feedproxy.google.com/~r/kent-spillner-work/~3/lE3s8htSwBM/github-pages.html" />
   <updated>2009-11-09T00:00:00-08:00</updated>
   <id>http://kent.spillner.org/blog/life/politics/work/2009/11/09/github-pages</id>
   <content type="html">&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;span class="caps"&gt;UPDATED&lt;/span&gt; 2010-09-18:&lt;/strong&gt; Added a new category to my site, &lt;a href="http://kent.spillner.org/blog/books/"&gt;Books&lt;/a&gt;, with &lt;a href="http://feeds.feedburner.com/kent-spillner-books"&gt;its own separate feed.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I finally found a blogging platform to love: &lt;a href="http://pages.github.com/"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; rocks!  It gives me control over every aspect of my site: directory structure, page layout, permalink format.  Everything is stored in plain text, versioned (courtesy of Git, natch), and cloudified.  There is no dumb blog name or stupid subtitle, no biography or mugshot, no comments or tag clouds.  I especially like the lack of comments because the best conversations happen when everyone just listens to me.&lt;/p&gt;
&lt;p&gt;The sheer awesomeness of &lt;a href="http://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; and its simplicity of use compel me to blog; they also compelled me to upgrade my GitHub account and wire-up a vanity domain.  I resisted lending my voice to the symphony of idiots blogging on the internets for many years, but no more!  &lt;a href="http://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; makes it so damn easy, I just can&amp;#8217;t help myself.  If I fail in this endeavour, if I fail to update regularly or at all, it will be for lack of trying, not inadequacy of the tool.&lt;/p&gt;
&lt;p&gt;The world suffers for lack of access to my opinions.  To make it easier to learn my opinion on a particular subject, blog entries will be categorized as one of: &lt;a href="http://kent.spillner.org/blog/life/"&gt;life&lt;/a&gt;, &lt;a href="http://kent.spillner.org/blog/politics/"&gt;politics&lt;/a&gt;, or &lt;a href="http://kent.spillner.org/blog/work/"&gt;work&lt;/a&gt;.  This entry itself is a special case and will be categorized as all three to seed my syndication feeds.  There are separate feeds &lt;a href="http://feeds.feedburner.com/kent-spillner-life"&gt;for&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/kent-spillner-politics"&gt;each&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/kent-spillner-work"&gt;category&lt;/a&gt;, and a fourth &lt;a href="http://feeds.feedburner.com/kent-spillner"&gt;feed with everything&lt;/a&gt;, to provide the illusion of choice.  In reality, all of my opinions are equally valuable.&lt;/p&gt;
&lt;p&gt;You&amp;#8217;re welcome.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/kent-spillner-work/~4/lE3s8htSwBM" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://kent.spillner.org/blog/life/politics/work/2009/11/09/github-pages.html</feedburner:origLink></entry>
 
</feed>

