views:

203

answers:

6

Someone designed code that relied on full data; the XML always had every element. The data source is now sending sparse XML; if it would have been empty before, it's missing now. So, it's time to refactor while fixing bugs.

There's 100+ lines of code like this:

functionDoSomething(foo, bar, getRoot().getChild("1").getChild("A").
    getChild("oo").getContent());

Except now, getChild("A") might return null. Or any of the getChild(xxx) methods might.

As one additional twist, instead of getChild(), there are actually four separate methods, which can only occur in certain orders. Someone suggested a varargs call, which isn't a bad idea, but won't work as cleanly as I might like.

What's the quickest way to clean this one up? The best? "try/catch" around every line was suggested, but man, that's ugly. Breaking out the third argument to the above method into it's own function could work... but that would entail 100+ new methods, which feels ugly, albeit less so.

The number of getChild(xxx) calls is somewhere between six and ten per line, with no fixed depth. There's also no possible way to get a correct DTD for this; things will be added later without a prior heads up, and where I'd prefer a warning in the logs when that happens, extra lines in the XML need to be handled gracefully.

Ideas?

getChild() is a convenience method, actually. The cleanest way I have in mind is to have the convenience methods return a valid Child object, but have that "empty" Child's getContent() always return "".

+1  A: 

The solution is to use a DTD file for XML. It validates your XML file so getChild("A") won't return null when A is mandatory.

True Soft
I've tried to pry an actual DTD from the hands of the business requirements folks. I don't have one, and it keeps slightly changing on me. The previous solution let it fail gracefully when new things were thrown in, at least.
Dean J
A: 

If it always drills down to roughly the same same level, you can probably refactor the code using Eclipse, for example, and it'll automatically change every line that looks the same.

That way you can modify the method to be smarter, rather than modifying each line individually

Malaxeur
No such luck, unfortunately. It drills down to any one of a half dozen levels, six to ten levels deep, with varying elements depending on which way it branches.
Dean J
I've edited the question to clarify that one.
Dean J
+8  A: 

What you described (returning a special child object) is a form of the NullObject pattern, which is probably the best solution here.

Kathy Van Stone
Yeah, it's working now, although not as clean as if I rewrote some code into XPATH.
Dean J
+2  A: 

How about:

private Content getChildContent(Node root, String... path) {
    Node target = root;
    for ( String pathElement : path ) {
         Node child = target.getChild(pathElement);
         if ( child == null ) 
            return null; // or whatever you should do

         target = child;
    }

    return target.getContent();

}

to be used as

functionDoSomething(foo, bar, getChildContent(root, "1", "A", "oo"));
Robert Munteanu
The actual call uses .getChild(xxx) between six and ten times per line, with no fixed depth.
Dean J
The path is a var-arg, which is converted into an array, so you can have the path take as many arguments as you like. Doesn't that fit your use case? E.g. you can use getChildContent(root,"a","b") and getChildContent(root,"d","e","f") with this signature.
Robert Munteanu
There are different types of children, actually, which tosses in another twist. getEle, getComp, getLoop, and getSeg, each of which can occur in different orders with different potential children. Seems like I stripped too much out of the original question, will add that detail back in.
Dean J
+8  A: 

Please consider using XPATH instead of this mess.

Kevin Bourrillion
That's not a bad idea, being that the mappings I do have are given to me in (half correct) XPATH.
Dean J
This was my initial reaction upon seeing the line of example code. It just screams for using XPath.
David
+1  A: 

Your problem could be a design problem: Law of Demeter.

If not you can use something like an Option type changing the return type of getChild to Option<Node>:

for(Node r : getRoot())
  for(Node c1 : r.getChild("1"))
    for(Node c2: c1.getChild("A"))
      return c2.getChild("oo")

This works because Option implements Iterable it will abort when a return value is not defined. This is similary to Scala where it can be expressed in a single for expression.

One additional advantage is that you can define interfaces that will never return a null value. With an Option type you can state in the interface definition that the return value may be undefined and the client can decide how to handle this.


Thomas Jung