Sunday 21 October 2007

Flame bait

I bumped into one of my former project managers a few months back. We talked a little about how things were going on the old project (which by all accounts goes live tomorrow... 3 years and 7 months late) and how things were for me in my new position. I told him a bit about XP and how much I enjoyed it. He seemed rather sceptical about how it could be costed. A couple of days later I came across this article and forwarded it to him.

Sometime later he replied. Part of his reply was, as he put it, "a bit controversial to stimulate the debate". After a few days of thought and few weeks of procrastination I replied, here it is:


PM: The Current Climate – the software industry does not have a good track record in delivering large projects. Therefore, customers are very nervous about entering into what they see as an open ended contract. From experience, it is very difficult to secure funding when you can’t guarantee what will be delivered at the end or when.

Having spent most of my IT career in the public sector I had no reply to this. I suppose I could have argued the "what will be delivered at the end" saying that a customer would get exactly what the customer wants and needs, but I guess his point was about funding.

PM: Design – the agile approach assumes an ongoing design process. The software industry is pretty unique in this regard. In most other industries (eg construction), no-one would contemplate starting a project without a completed design……why should software be any different? I have bitter experience of developers having to re-factor code (which really meant rip it up and start again) because module x no longer worked with the new architecture.

Me: Please tell me you aren't trying to compare the job of a programmer to a brick layer? The waterfall approach of Big Design Up Front has been almost completely discounted by the agilists. The reason I see for this reaction is that, at a detailed level it's almost impossible to fully understand every eventuality/path through a software system. If you were to try to envisage and document every scenario, my guess is that you would have spent as much (if not more) time than it would have taken to develop a working system. Not a complete working system that meets all of their business needs, but one that could meet their most critical business needs earlier and could be put into production and be of value.

Another major issue (which ties back to your earlier point) is that customers don’t know what they want, especially not from a large system and they can’t be expected to. They are the best people to understand the domain problem they have of course, but not the best solution that solves that problem especially not upfront. That’s why designing and implementing a large system before letting a customer use it is why, I guess, the software industry has a bad reputation.

As for refactoring, it is about refining code to: increase quality; re-useability; maintainability; simplicity; etc, all of the things that reduce bugs, please customers and make applications easier and cheaper to maintain and modify going forward. Refactorings like you suggest, where large steps are taken to rewrite areas of code, occur when the processes of incremental design and refactoring are not put in place.

PM: Quality – the quality argument in the article seemed to skip over the fact that a bad programmer is a bad programmer no matter what methodology you use. The article (and fans of agile programming) suggests that agile programming is the panacea and will solve all the quality problems….garbage! At the end of the day, the customer still has to have a working system (i.e., bug-free).

Me: A bad programmer is a bad programmer I agree. But with techniques like test driven development (TDD) and pair programming a bad programmer can become a better programmer (I won’t go as far to say a good programmer). But a bad programmer who understands and practices TDD, commits code regularly to a code base which has automated continuous integration can produce reliable code. Where as a bad programmer left on there own without writing tests first can end up in a spaghetti junction of bad code with no way of verifying the quality of their work. Either way good or bad programmers who don’t write tests first can write code that’s buggy.

I agree that Agile development methods aren’t a panacea to bug free code, good developers play a big part. But I do honestly believe that projects benefit massively with agile techniques like:

  • TDD;
  • Refactoring;
  • Continuous integration;
  • Pair programming;
  • Short development iterations;
  • Daily stand up meetings.
They increase:
  • Code quality;
  • Understanding of requirements;
  • Understanding of a code base throughout a team, which for one helps reduce risk when team members move on;
  • Communication both internally and externally.

Friday 17 August 2007

My worst interview

Q: So tell me the difference between Statement, PreparedStatement, and CallableStatement and when you’d use each.
A: Um, you use a Statement to perform a query from a database.

http://burtbeckwith.com/blog/?p=44

Minus the ridiculous number of buzzwords on the CV, this could have been an interview I attended 3 years ago. I had a howler!! TBH I was a bit green back then and the interview was a wake up call.

Thursday 5 April 2007

The new job

So thats the end of week 2 in the new job and I'm shattered. I've already learned loads and I've still got a mountain to climb. I've arrived just in time to do my objectives and they are looking pretty full up already. I plan to sit the SCJP (I think I've put it off long enough) learn Ruby strengthen the old SQL skills whilst trying to soak up as much domain knowledge as possible.

Friday 23 March 2007

Lunch

Lunch is the most important meal of the day! Well maybe not but if you like a sandwich, like I do, it's possibly the most enjoyable. So here are my fav sandwich shops in Glasgow:
  1. Where the Monkey Sleeps;
  2. MYO;
  3. ItaliaMania;
  4. M&S;
My goal for the remainder of this year is to stretch this list to 10 and get M&S out of the top 5, at least.

The last day and the week before the first day.

So that's it, I've written my last line of code for the government (never say never of course).

I had a great night out with the folks at the old place, drank plenty of beer and danced till about 3 or 4 in the morning. Many thanks to all that could make it. For those that couldn't, I'm sure there will be many other opportunities in the coming months to share a drink or two. Special thanks to Johno who made the trip up from Manchester for the occasion, to Kenny for taking loads of great pics and to Monica and Michael for getting me in a taxi home. Thanks also to Niall for the leaving speech, I really appreciate all the nice things you said.

During the last week, between jobs, I've had a gas with Jules and the kids and have managed to get a few jobs done around the house. It's been really nice spending this time with the Tom and Maisie, they really are coming along a treat. It's also helped me appreciate even more what a great job Jules does.

Jules and I also managed to get a night out alone (kid free) together and took a trip down memory lane. My big bro's band, that we used to watch at every opportunity back in the 90's, played at a friends 40th. It was great to see them up there again, tight as as usual.

Wednesday 28 February 2007

A new start

After almost 5 1/2 years in my first job since graduating I've finally decided to move on. I'm off to work for an investment bank, which I'm expecting to be a culture shock considering my current cosy public sector position. I have to admit I'm really excited about the opportunity to work in a proper agile/xp environment and that has been my main reason for accepting this job over others. Being a sentimental kind of guy I have to look back over these last years at what have I achieved, I've:
  • Got married to Julia;
  • Had two beautiful children, Tom (Jan 12 2005), Maisie (Nov 15 2006);
  • Bought my first then second house;
  • Led a team of great developers to develop one of the most high profile systems in the criminal justice domain;
  • Learned loads of acronyms... OOP, AOP, XP, TDD, DDD, EJB(painfully), JMS(as painfully), JSP and I'm sure many other that don't Spring (see what i did there) to mind;
  • Read loads of great software development books;
  • loads of other stuff.

Thursday 15 February 2007

You must read

I was talking to some guys from another dept in the pub the other night and one of them mentioned Patterns of Enterprise Application Architecture by Martin Fowler. We discussed it a little and I then brought up a few other texts which I have read and was surprised that no one else had heard of them. So that prompted me to write this. I've been reading software development books regularly now for about 3 years and this is the list I think is essential reading. If you are in the same boat I'm afraid you'll find nothing too original, if not I hope you are inspired... not by me of course, but by the texts themselves.

Title: Extreme Programming Explained
Author: Kent Beck
Why: This book explains how software should be developed... IMHO. It details how to plan, design, code, and test in a easily digestible and, more importantly, inspiring way. It introduced to me great concepts like pair programming, iterative development, test driven development and stand up meetings etc.

Title: Test Driven Development
Author: Kent Beck
Why: This book holds your hand through the process of TDD. Once you have finished reading this you should know exactly how to code test first and, more importantly, not want to do it any other way.

Title: Domain Driven Design
Author: Eric Evans
Why: After reading XP explained I often wondered about how to gain and manage domain knowledge effectively... this book explains it. It explains how to manage Domain Models across large teams and introduces some great design patterns, my favorite being the Value Object pattern (not to be confused with the J2EE DTO pattern). It also shows you how to manage domains in a number of different environments.

Title: Refactoring
Author: Martin Fowler
Why: In the same way Kent Becks book teaches TDD, this book holds your hand through the process of refactoring. It then goes on to introduce a whole host of code 'smells' and refactorings that can be applied to them. As an aside i think the term 'refactoring' is being abused at the moment. It is being used to denote large scale code redesigns or even ground up redevelopment. From my understanding of MFs book this isn't what refactoring is about.

Title: The Pragmatic Programmer (From Journeyman to Master)
Author: Andy Hunt & Dave Thomas
Why: Like the Ronseal adverts this "does exactly what it says on the tin". It introduces an number of great approaches that all good/great programmers should abide by. Things like the DRY principal (Don't Repeat Yourself) and how they apply this to the whole spectrum of systems analysis, design and development and other gems like Tracer Bullets and Broken Windows are priceless.

There are a number of other great books I toyed with putting on my list. P of EAA was a contender along with Rod Johnsons J2EE Design and Development, I also considered Bruce Tates Better Faster Lighter Java. These are all books that I have really enjoyed and have been taken with. However, I had to draw the line somewhere.

BTW if I had read the GoF book I'm sure it would have been there... I promise to get my finger out and read it this year. As much as the Head First Design Patterns book covers most of the patterns, I know, it's just not the same.

Sunday 28 January 2007

Harry Potter Kata

I was asked about a week or so ago if I knew what a Kata was, I said that I didn't. If I had taken my time to think about it I may have remembered those practice karate moves my brother showed me a few years ago. If I had taken that time, then my answer wouldn't have been a million miles away. I won't try and explain, take a quick look here. Shortly after being asked I was set a little challenge. What follows is my attempt at the Harry Potter Kata, in Java (of course), hopefully I'll be able to give it a go in Ruby some day. Step 1, I'm going to write a test for the first acceptance of the criteria. No order, no charge.
   public class BookstoreTest extends TestCase {
       private Bookstore bookstore;

       protected void setUp() throws Exception {
           super.setUp();
           bookstore = new Bookstore();
       }

       public void testNoBooksOrdered() {
           assertEquals(0.0, bookstore.totalPrice(new int[]{}));
       }
   }
A red light as expected. Next step, a simple implementation that returns 0.
   public class Bookstore {

       /**
        * Calculates the total price of the order placed.
        *
        * @param orders - an array of ints representing orders
        * @return total cost
        */
       public double totalPrice(int[] orders) {
           return 0;
       }
   }
Ok, that was simple but I like this approach. I find it helps set a tempo, only do enough to pass the test (and keep all the others passing of course). Next up isn't going to be anymore difficult but again it doesn't have to be in order to pass the test. Here are the new tests. They assert that one order placed is charged at the price of one volume.
   public void testOneBookInRangeOrdered() {
       assertEquals(8.0, bookstore.totalPrice(new int[]{0}));
       assertEquals(8.0, bookstore.totalPrice(new int[]{1}));
       assertEquals(8.0, bookstore.totalPrice(new int[]{2}));
       assertEquals(8.0, bookstore.totalPrice(new int[]{3}));
       assertEquals(8.0, bookstore.totalPrice(new int[]{4}));
   }
They all fail, a simple change and all tests should pass.
   public double totalPrice(int[] orders) {
       return 8 * orders.length;
   }
As expected all tests now pass. Time for another test, this time test that 2 copies of same volume are not discounted.
   public void testTwoBooksOfSameVolumeOrderedProvidesNoDiscount() {
       assertEquals((double) 8 * 2, bookstore.totalPrice(new int[]{1, 1}));
   }
This time no need to change any implementation code, all tests pass. The next test will assert that 3 copies of the same volume are not discounted.
   public void testTwoBooksOfSameVolumeOrderedProvidesNoDiscount() {
       assertEquals((double) 8 * 2, bookstore.totalPrice(new int[]{1, 1}));
   }
Again all tests pass. Ok that's all the basic cases catered for, now to have a go at the simple discounts. First up two different volumes.
   public void testDiscountAppliedToTwoUniqueVolumes() {
       assertEquals((double) 8 * 2 * 0.95, bookstore.totalPrice(new int[]{1, 2}));
   }
Red light as expected, my implementation is returning 16 but 15.2 is required. So I need to now start taking into account the order type. If I create a Map that holds each order type requested as a key and the count of each type then I should then be able to calculate the cost. Ok that should be simple enough to test and I think the implementation should merit it's own method.
   public void testTwoMapEntriesCreatedEachWithACountOfOne() {
       Map<Integer, Integer> orderCounts = new HashMap<Integer, Integer>();
       orderCounts.put(1, 1);
       orderCounts.put(2, 1);
       assertEquals(orderCounts, bookstore.createVolumeCountMap(new int[]{1, 2}));
   }
I'm perfectly happy to implement the createVolumeCountMap as a package scope method in order to test it. I don't see any harm in it, we are solving a domain problem not creating an API.
   Map createVolumeCountMap(int[] orders) {
       Map<Integer, Integer> orderCounts = new HashMap<Integer, Integer>();
       for (int i = 0; i < orders.length; i++) {
           if(orderCounts.containsKey(orders[i])){
               Integer count = orderCounts.get(orders[i]);
               count++;
               orderCounts.put(orders[i], count);
           }
           else{
               orderCounts.put(orders[i], 1);
           }
       }
       return orderCounts;
   }
Now that the orders are in a more manageable form we need to identify how many occurrences of each discount type there are.
   public void testSmallestCountForDiscountType1() {
       Map<Integer, Integer> orderCounts = new HashMap<Integer, Integer>();
       orderCounts.put(1, 3);
       orderCounts.put(2, 3);
       assertEquals(3, bookstore.identifySmallestCountForDiscountType(orderCounts, 1));
   }

   public void testSmallestCountForDiscountType2() {
       Map<Integer, Integer> orderCounts = new HashMap<Integer, Integer>();
       orderCounts.put(1, 3);
       orderCounts.put(2, 3);
       assertEquals(3, bookstore.identifySmallestCountForDiscountType(orderCounts, 2));
   }
After a couple of attempts this is what I came up with.
   int identifySmallestCountForDiscountType(Map<Integer, Integer> orderCounts, int discountType) {
       if (orderCounts.size() == discountType) {
           Integer smallestCountOfVolumes = null;
           Collection<Integer> values = orderCounts.values();
           for (Iterator<Integer> iterator = values.iterator(); iterator.hasNext();) {
               Integer volumeCount = iterator.next();
               if (smallestCountOfVolumes == null) {
                   smallestCountOfVolumes = volumeCount;
               } else if (volumeCount < smallestCountOfVolumes) {
                   smallestCountOfVolumes = volumeCount;
               }
           }
           return smallestCountOfVolumes;
       }
       return 0;
   }
Ok now I need to go back to the totalPrice method and start integrating some of this code. First thing is to convert the orders into the Map using the createVolumeCountMap method. Next thing that I see is the Bookstore needs some sort of repository for the discount type per unique number of volumes. Should be straightforward enough, just add a Map containing the number of unique volumes and the discount that can be applied. Now I can iterate over the discount types and identify how many of each are in the orderCounts map. With that I should then be able to start a running total. I'll also need to make sure I'm managing the orderCounts map, reducing the number of orders accordingly. So the TODO list at the moment is:
  • Use the createVolumeCountMap to convert the int[] into a Map
  • Create a repository for the discount type per unique number of volumes
  • Iterate over the discount types
  • Calculate the running total
  • Reduce the orderCounts map
I'll tackle reducing the orderCounts map first.
   public void testReduceMapEntriesByCount() {
       Map<Integer, Integer> orderCounts = new HashMap<Integer, Integer>();
       orderCounts.put(1, 3);
       orderCounts.put(2, 3);
       bookstore.reduceAllEntriesByCount(orderCounts, 3);
       assertEquals(0, (int) orderCounts.get(1));
       assertEquals(0, (int) orderCounts.get(2));
   }
Straightforward enough, introduced the reduceToNoLessThanZero to make sure that it can be tested more easily.
   void reduceAllEntriesByCount(Map<Integer, Integer> orderCounts, Integer reduction) {
       Set<Integer> keys = orderCounts.keySet();
       Integer count;
       Integer key;
       for (Iterator<Integer> iterator = keys.iterator(); iterator.hasNext();) {
           key = iterator.next();
           count = orderCounts.get(key);
           orderCounts.put(key, reduceToNoLessThanZero(count, reduction));
       }
   }

   public void testReductionToNoLessThanZero() {
       assertEquals(0, bookstore.reduceToNoLessThanZero(3, 4));
       assertEquals(1, bookstore.reduceToNoLessThanZero(4, 3));
   }

   int reduceToNoLessThanZero(Integer count, Integer reduction) {
       Integer newValue = count - reduction;
       return newValue >= 0 ? newValue : 0;
   }

Now I have the implementation to reduce the map entries I can now check off the rest of the TODO list. So totalPrice now looks like this.
   public double totalPrice(int[] orders) {
       Double total = 0.0;
       Map<Integer, Integer> orderCounts = createVolumeCountMap(orders);
       Set<Integer> discountTypes = uniqueVolumesPercentageCharge.keySet();
       for (Iterator<Integer> iterator = discountTypes.iterator(); iterator.hasNext();) {
           Integer discountType = iterator.next();
           Integer smallestNumberPerType = identifySmallestCountForDiscountType(orderCounts, discountType);
           total = addToTotal(total, smallestNumberPerType, discountType);
           reduceAllEntriesByCount(orderCounts, smallestNumberPerType);
       }
       return total;
   }
The addToTotal method is there, again, to make the code more readable and to ensure that it's properly tested.
   public  void testAddToTotal(){
       assertEquals(8.0, bookstore.addToTotal(0.0, 1, 1));
       assertEquals(16.0, bookstore.addToTotal(0.0, 2, 1));
       assertEquals(15.2, bookstore.addToTotal(0.0, 1, 2));
       assertEquals(23.2, bookstore.addToTotal(8.0, 1, 2));
   }

   Double addToTotal(Double total, Integer smallestNumberPerType, Integer discountType) {
       return total + (smallestNumberPerType * costPerVolume * discountType)
              * uniqueVolumesPercentageCharge.get(discountType);
   }

Ok now all lights are green!! Time to return to the acceptance criteria. Added all the simple discount tests and everything passes!! Added the 1st of the several discounts combined and it fails. Ok closer inspections shows that I’m not calculating the discount in the correct order, so I'll need to put them in reverse chronological order.
   public void testReverseOrder() {
       Set<Integer>originalOrder = new TreeSet<Integer>();
       originalOrder.add(1);
       originalOrder.add(2);
       originalOrder.add(3);
       originalOrder.add(4);
       originalOrder.add(5);

       Integer[] reverseOrder = bookstore.putInReverseOrder(originalOrder);

       assertEquals(new Integer(5), reverseOrder[0]);
       assertEquals(new Integer(1), reverseOrder[4]);
   }

   Integer[] putInReverseOrder(Set<Integer> discountTypes) {
       Set<Integer> orderKeySet = new TreeSet<Integer>(discountTypes);
       Integer[] orderedKeys = orderKeySet.toArray(new Integer[discountTypes.size()]);

       Arrays.sort(orderedKeys, Collections.reverseOrder());
       return orderedKeys;
   }
Green light for all of the several discount combined acceptance tests. Now for the vicious tests. As expected they fail, I knew it wasn't going to just fall into place! Ok so I'm calculating the cost for 1*5 and 1*3 different volumes and the test is asserting 2*4 in the 1st test. In the second test 4*5 and 1*3 different volumes as opposed to 3*5 & 2*4.
Before I go any further and start throwing code at it I'll take step back and have a look at what I've got. At the moment I'm creating a Map that has the count for each volume. So for vicious case one it looks like this...
VolumeNumber Ordered
0 2
1 2
2 2
3 1
4 1
I need it to look like this...
VolumeNumber Ordered
0 2
1 2
2 2
3 2
How am I going to do that? I'm not; the data is being stored in the wrong format. I need to create bundles not counts per volume, something like this...
Volume 1Volume 2Volume 3Volume 4Volume 5
X X X X
X X X X
So I’m going to have to do some refactoring. Best thing to do first is get the createVolumeCount method refactored to return a List and I'll go from there.
   public void testCreateVolumeBundlesForViciousForCaseOne() {
       List<Set> orderBundles = new ArrayList<Set>();

       Set<Integer> bundle1 = new HashSet<Integer>();
       bundle1.add(0);
       bundle1.add(1);
       bundle1.add(2);
       bundle1.add(3);
       orderBundles.add(bundle1);

       Set<Integer> bundle2 = new HashSet<Integer>();
       bundle2.add(0);
       bundle2.add(1);
       bundle2.add(2);
       bundle2.add(4);
       orderBundles.add(bundle2);

       assertEquals(orderBundles, bookstore.createVolumeBundles(new int[]{0, 0, 1, 1, 2, 2, 3, 4}));
   }

Cracked it!
   List<Set> createVolumeBundles(int[] orders) {
       List<Set> bundles = new ArrayList<Set>();
       for (int order : orders) {
           Set<Integer> bundle = findSmallestAvailableBundle(bundles, order);
           bundle.add(order);
       }
       return bundles;
   }

   private Set<Integer> findSmallestAvailableBundle(List<Set> bundles, int order) {
       for (Set<Integer> bundle : bundles) {
           if (!bundle.contains(order) && !containsSmallerAvailableBundle(bundles, bundle, order)) {
               return bundle;
           }
       }
       return createNewBundle(bundles);
   }

   private boolean containsSmallerAvailableBundle(List<Set> bundles,
                                                  Set<Integer> bundleBeingConsidered, int order) {
       for (Set<Integer> bundle : bundles) {
           if (!bundle.contains(order) && bundle.size() < bundleBeingConsidered.size()) {
               return true;
           }
       }
       return false;
   }

Ok so now almost all of my other tests fail. Instead of weeding out the redundant tests and code, I’ll re-implement the totalPrice method and then remove the redundant code. totalPrice now looks like...
   public double totalPrice(int[] orders) {

       Double total = 0.0;
       List<Set> orderBundles = createVolumeBundles(orders);
       for (Set<Integer> bundle: orderBundles){
           total = addToTotal(total, bundle.size());
       }
       return total;
   }
Typical, now 2 of the several discounts at once tests fail. What I think I need to do now is create another list of bundles only this time instead of having them contain the smallest possible bundles have them contain the natural/largest bundles. Then compare the cost of each bundle and return the lowest cost.
   public void testCreateVolumeBundlesForSeveralDiscountsTest3() {
       List<Set> orderBundles = new ArrayList<Set>();

       Set<Integer> bundle1 = new TreeSet<Integer>();
       bundle1.add(0);
       bundle1.add(1);
       bundle1.add(2);
       bundle1.add(3);
       orderBundles.add(bundle1);

       Set<Integer> bundle2 = new TreeSet<Integer>();
       bundle2.add(0);
       bundle2.add(2);
       orderBundles.add(bundle2);

       assertEquals(orderBundles, bookstore.createLargestVolumeBundles(new int[]{0, 0, 1, 2, 2, 3}));
   }

   List<Set> createLargestVolumeBundles(int[] orders) {
       List<Set> bundles = new ArrayList<Set>();
       for (int order : orders) {
           Set<Integer> bundle = findAvailableBundle(bundles, order);
           bundle.add(order);
       }
       return bundles;
   }

   private Set<Integer> findAvailableBundle(List<Set> bundles, int order) {
       for (Set<Integer> bundle : bundles) {
           if (!bundle.contains(order)) {
               return bundle;
           }
       }
       return createNewBundle(bundles);
   }
A slight modification to totalPrice and all lights are green!! After all that it boils down to 91 lines of code...
import java.util.*;

public class Bookstore {

   private Map<Integer, Double> uniqueVolumesPercentageCharge = new HashMap<Integer, Double>();
   private int costPerVolume;

   public Bookstore(Map<Integer, Double> uniqueVolumesPercentageCharge, int costPerVolume) {
       this.uniqueVolumesPercentageCharge = uniqueVolumesPercentageCharge;
       this.costPerVolume = costPerVolume;
   }

   /**
    * Calculates the smallest total price of the order placed.
    *
    * @param orders - an array of ints representing orders
    * @return total cost
    */
   public double totalPrice(int[] orders) {

       Double smallBundlesTotal = calculateCostOfOrders(createSmallestVolumeBundles(orders));
       Double largeBundlesTotal = calculateCostOfOrders(createLargestVolumeBundles(orders));
       return smallBundlesTotal < largeBundlesTotal? smallBundlesTotal:largeBundlesTotal;
   }

   private Double calculateCostOfOrders(List<Set> orderBundles) {
       Double total = 0.0;
       for (Set<Integer> bundle: orderBundles){
           total = addToTotal(total, bundle.size());
       }
       return total;
   }

   Double addToTotal(Double currentTotal, int bundleSize) {
       return currentTotal + (costPerVolume * bundleSize
               * uniqueVolumesPercentageCharge.get(bundleSize));
   }

   List<Set> createSmallestVolumeBundles(int[] orders) {
       List<Set> bundles = new ArrayList<Set>();
       for (int order : orders) {
           Set<Integer> bundle = findSmallestAvailableBundle(bundles, order);
           bundle.add(order);
       }
       return bundles;
   }

   private Set<Integer> findSmallestAvailableBundle(List<Set> bundles, int order) {
       for (Set<Integer> bundle : bundles) {
           if (!bundle.contains(order)
                   && !containsSmallerAvailableBundle(bundles, bundle, order)) {
               return bundle;
           }
       }
       return createNewBundle(bundles);
   }

   private boolean containsSmallerAvailableBundle(List<Set> bundles,
                                                  Set<Integer> bundleBeingConsidered, int order) {
       for (Set<Integer> bundle : bundles) {
           if (!bundle.contains(order) && bundle.size() < bundleBeingConsidered.size()) {
               return true;
           }
       }
       return false;
   }

   List<Set> createLargestVolumeBundles(int[] orders) {
       List<Set> bundles = new ArrayList<Set>();
       for (int order : orders) {
           Set<Integer> bundle = findAvailableBundle(bundles, order);
           bundle.add(order);
       }
       return bundles;
   }

   private Set<Integer> findAvailableBundle(List<Set> bundles, int order) {
       for (Set<Integer> bundle : bundles) {
           if (!bundle.contains(order)) {
               return bundle;
           }
       }
       return createNewBundle(bundles);
   }

   private Set<Integer> createNewBundle(List<Set> bundles) {
       Set<Integer> bundle = new TreeSet<Integer>();
       bundles.add(bundle);
       return bundle;
   }
}