tags:

views:

512

answers:

11

Does the rigidity of clear interfaces mean that flexible code must be ugly? I'll use Java and Perl as respective examples.

Clarification: it's not that code being messy makes it flexible; but that code being flexible makes it messy.

Clarification: "flexibility" almost has a different meaning in exploratory code vs older code. Perhaps "raw flexibility" vs. "compatible flexibility"? I'm talking about "raw flexibility" - thanks to Mario Ortegón

Java

Java pays much attention to compatibility: the content of the language and standard libraries are steeped in OO design, and it even has the extensively used keyword interface; the JVM and language themselves have specifications of external behaviour. Sun has a history of clear and open specifications (e.g NFS).

Designing a clear interface that works well is very difficult to do - but when done right, the result can be very clear. A specification (including an OO design) is by its nature rigid - any flexibility it has is rigidly circumscribed by the spec.

The result is that if you correctly predicted the the degrees of freedom needed, the design has great clarity and precision - it will actually act as a guide towards the right questions to ask, and give you useful terms with which to think about the problem.

Perl

One spirit of Perl is "there's more than one way to do it". and the language syntax itself has evolved considerably over the years (including incompatible changes). Like Java, this attitude carries on into programs written in the language.

Although one can write Perl to clear interfaces, it isn't enforced, and often isn't done. This can result is "write only" code - but if you understand it, you can change it in arbitrary ways. That is, it is ugly but flexible - perhaps my definition of ugly needs to change?

Apparently mjd said of Higher Order Perl that he couldn't have written it with test-driven development, because it would lack the flexibility he needed. I hope I'm not putting words in his mouth here, but I think the same would apply to interfaces and specifications (because that is something that TDD enforces).

It seems to me that Java code tends to be neat but rigid; and Perl code tends to be messy but flexible. Java favors upfront design; and Perl favors rapid prototyping.

In my code (Java) I keep facing a choice between neat vs. flexible. For example, I compared a Visitor pattern against one big function of cascading if's.

The latter is ugly, but it is many times easier to add parameters to the recursive method calls (this is only a problem if you need that degree of flexibility (which I find I do in exploratory prototyping).

Clarification: The visitor pattern refactors the cascading-if's as method overriding, at the cost of defining and calling those methods, one class per if, so that an added argument must be added to all those methods and all the calls of them, in all those classes. I coded these designs and counted how many places needed arguments added: 9 places for Visitor (across different files); 3 places for cascading-ifs (all in one file). The cascading-ifs solution is much easier to modify, even though it's ugly.

This question isn't about Java and Perl - I'm just using them as illustrative examples to make the question more concrete and definite, and hopefully easier to consider.

Is arbitrarily flexible code necessarily messy?

+5  A: 

No, I don't think so. I think this is very subjective but also I don't agree with your examples. You wrote:

For example, I compared a Visitor pattern against one big function of cascading if's. The latter is ugly, but it is many times easier to add parameters to the recursive method calls (this is only a problem if you need that degree of flexibility, which I find I do in exploratory prototyping.)

However, I think you could make the "cascading ifs" go away with an application of polymorphism. For example:

http://googletesting.blogspot.com/2008/12/by-miko-hevery-google-tech-talks.html

You say the cascading ifs is more flexible, if I understand you correctly, I disagree. They might be more flexible. I doubt it.

How flexible is "messy" code? Is spaghetti code really easy to modify without invoking fears of "have I broken something." I rarely worry about whether another implementation of an interface will break existing code as much as I worry about breaking messy, spaghetti code with minor changes.

Furthermore, the point of all this is flexibility. The point is not just "gee, let's just use all these patterns and polymorphism because it is 'neater.'" The idea is to be more flexible and reusable. I think clarity is a side effect of that as well.

Sure, there might be some amount of cases where just adding an extra "if" is easier but I wouldn't say that is "flexible." We might be using different defintion of "flexible," though.

BobbyShaftoe
I didn't mean that messy code is flexible, but that flexible code is messy. I think I wasn't clear on that. Thanks, clarification added to question.
13ren
Yes, "cascading ifs" can be replaced with polymorphism - giving the visitor pattern I contrasted it with. The visitor pattern refactors if's as method overriding, at the cost of defining and calling those methods, one class per if. Any added arguments must be added to all of them.
13ren
+3  A: 

Hmm, tough question :-)

I think that flexible code can be much easier and cleaner than specialized code, but it requires a lot of thinking and practice. I'd put it this way:

  • specialized code is easy in the start and gets harder to keep clean when the requirements change
  • flexible code is harder to structure in a clean way in the start and when done right, then it's easier to adopt new requirements due to the flexibility
Patrick Cornelissen
Not so sure about the last statement. Too flexible, and then you start having spooky effects at a distance
Mario Ortegón
What you say about "specialized code" and "flexible code" seems to suggest they are both clearly specified at the start, but the flexible code's specification more accurately anticipates the degrees of freedom required.
13ren
You have to make some assumptions, right. In most cases you can anticipate what needs to flexible by doing educated guesses- This of course doesn't protect you from strange new requirements, which are always a pain to implement in a clean way.
Patrick Cornelissen
+8  A: 

It often works the other way around,

I mostly work in C# and Python, so that might explain my difference in experience.

Code that's clean, elegant and simple is often more reusable and flexible than code that's contrived and ugly. It's easier to understand and therefore easier to adapt.

When I compare C# code (comparable to java) and Python code I often find the python code cleaner and more elegant, there is less rigid "ceremony" around declaring types etc. That makes the code cleaner and also more flexible.

That said you can make code more flexible and less clear. If you make code flexible by adding extension points explicitly it's it will often become less elegant.


@13ren: Great point about "invisible extension points", not only in Python but in most dynamically typed languages. Languages that use "duck-typing" automatically assume you "program to an interface, not an implementation". That makes code flexible and extensible. The problem is that you don't define your interfaces any more, everything automatically is an interface. In C# and Java you have to do that by hand, In many cases this is 'ceremony' and gets in the way. In many other cases the power to define your interface explicitly gives the developer power.

Mendelt
I agree, it seems odd to equate messy code with flexible. :)
BobbyShaftoe
Yes, Java's part of it: "explicit extension points" due to "rigid ceremony". It's a lot of work is you can only vary by explicitly specifying the variations; and leaving things open and loose is sloppy. This implies Python has invisible extension points: out of sight, out of mind.
13ren
I'm now thinking that arbitrarily flexible code needs to be "loose". Java's verbose ceremony doesn't help, but it's secondary. To me, loose code isn't neat, because it doesn't precisely and exactly nail the problem. But let's face it, "arbitrarily flexible" == "loose", by definition.
13ren
@BobbyShaftoe the question wasn't equating messy and flexible, but saying that flexible code is messy (not that messy code is flexible). Clarification added.
13ren
+2  A: 

I don't think rapid prototyping means more flexible code. Sure it is more flexible at the start, but as the application grows, it becomes harder and harder to mantain. Theoretically the use of interfaces makes it easier to keep unrelated parts separated.

At the end of the day, though, it all depends on the design.

Let's say there is a curve. If the application is complex, upfront design is necessary to be able to add new features in a timely manner. If the application is simple, then it might be better to use something more flexible.

Mario Ortegón
You're absolutely right: "flexibility" almost has a different meaning in exploratory code vs older code. Perhaps "raw flexibility" vs. "compatible flexibility"?
13ren
Keeping the unrelated parts in separate modules is very helpful ("theoretically" as you carefully say) - but the that choice of separation itself becomes hard to change.
13ren
That is indeed a problem. I remember some heated discussion between using interfaces and abstract base classes, because interfaces are hard to change once everyone uses them.
Mario Ortegón
Are abstract base classes easier to change because you can add non-abstract methods with a default implementation, without breaking subclasses that don't define them? Whereas interface methods are always abstract and implementations *must* define them.
13ren
Exactly that discussion. There was a lot of pros and cons going around :D
Mario Ortegón
+1  A: 

I think one issue is the number of choices available. "The Design of Everyday Things" says that usability is increased by having few and clear choices (called "actions").

By limiting the choices available, a specification increases usability for the envisioned task and usage. But this necessarily decreases flexibility in other, unforeseen, ways. Each hook available for arbitrary flexibility is adding a choice - which makes it more general, but less usable for specific tasks.

Few choices --> neat but rigid.

Many choices --> messy but flexible.

13ren
You can build an entire computer out of NAND gates (or NOR gates, but one generally doesn't), which have completely defined and completely rigid interfacs, are very simple and infinitely arbitrarily flexible.
Peter Wone
@Peter there's something important in that, but note that Perl instructions are also rigidly defined. A collection of NAND gates without constraints would fall into the "many choices->mess" category. If constraints are imposed (eg register modules, ALU etc), it moves towards "fewer choices->neat".
13ren
+2  A: 

If you think about flexibility early in your design, then you take the risk of having no idea of what difficulties you will face. One advice of code complete is to write a modular reusable component when you are reimplementing the same thing for the third time.

Flexibility is hard. That is what refactoring is all about ! Improve your code as you add features to it. Or you can just add features 'as you go', and end up with a pile of ugly code. But may be the ugly path has to be taken so that you can gather experience and idea about how to improve both flexibility and maintainability

shodanex
+1 for flexibility is hard. Definitively making code good for the future means investing more time upfront
Mario Ortegón
Good advice and it applies to just about every language. Strict interfaces can be very messy if designed wrong. Flexible interfaces can be extremely clean if they are designed right. A good designer won't have problems making clean Perl and a bad designer will still have problems designing Java code
mpeters
+1  A: 

I think you have it all backwards.

Cleanly designed, well modularized code is as flexible as it gets. Look at the tons of libs available for java. The libs that are used a lot a cleanly designed. They solve one problem and they solve it well.

This gives the developer using those the flexibility to decide what he needs and pick the libs necessary to do the job.

The single lib is flexible in the sense that you can use it in many different places.

Your Big Blob of Mud sorry if statements is not flexible at all. It is hard wired to all the stuff it calls. You want to use it in a different place? Sorry, can't do that without changing the code.

What you are really seeing is: Considering the design, implementation and architecture of code as flexible in the sense of 'everybody can change it at will, no matter if he knows anything about design' results in ugly code. Which soon is so rigid and frozen, that you only can throw it a way, and it wouldn't even go down the toilet, because it is so rigid.

Jens Schauder
+3  A: 

Generally, clean code is easier to bend to your will. It is possible to design an interface that is simple and flexible.

Your mention of HOP helps to highlight one of the easiest ways to give huge flexibility to your APIs--provide the ability to pass in a function reference (or whatever you call it in your language of choice).

The Perl sort function is a perfect example of this approach. Pass a code ref and a list, and get a sorted list. The code ref works with localized variables $a and $b. If the code returns 1, $a comes after $b, return 0 to set an equivalence, and -1 to put $b before $a.

Where does the power of this approach come from? It works so well because it encapsulates the choices. The idea behind the Visitor pattern is the same. Unfortunately. Java is such a verbose language that it costs more to implement and maintain than a bunch of if else if else if crud.

Power, flexibility and simplicity are the result of a good design. It's possible to create inelegant crap in any language (or any design medium).

daotoad
Java's verbosity sure doesn't help. Note: Visitor encapsulates some choices, and not others. If you need the other choices (e.g. to pass an extra argument), you must change methods and calls on the visitor, and on the visited data structure. Do Perl functions take a variable number of arguments?
13ren
When you call a Perl subroutine, all the arguments are stored in the @_ array, which contains aliases to the function arguments. The number and type of arguments is not limited. Usually people unpack @_ into various lexically scoped variables to avoid accidentally modifying the caller.
daotoad
Thanks - that makes it a bit easier to adapt. Java now has a similar facility for variable numbers of arguments (T... args), or I could just use an array for arguments explicitly. A little ugly due to Java syntax, but also very loose, which is also a kind of messiness to me (maybe I'm wrong).
13ren
+1  A: 

I think one issue is the number of choices available. "The Design of Everyday Things" says that usability is increased by having few and clear choices (called "actions").

By limiting the choices available, a specification increases usability for the envisioned task and usage. But this necessarily decreases flexibility in other, unforeseen, ways.

Well I think that's DoET a little backwards. With the example a door's push plate, the door already opens one way. The idea of an affordance is to make it clear which way it opens. The limitation comes first and the clarity comes next. The limitation has more to do (I believe) with safety and security. (Safety because each time the door opens we anticipate where it swings and security because it can be reinforced in a chosen direction for barring entry.)

I really don't think we limit choices so that we know how do open a door, because we can make them swing if we wanted. Thus it's once the limits have been established that the push plate makes it clear what you have to do on your side of the door.

Axeman
Maybe you're right. Communicating choices via affordances is important, but it's also easier to communicate them if the design has already constrained the options. A door's intrinsic "design" has already constrained them to push or pull.
13ren
Having a simple conceptual model is important, and you need to communicate it (via affordances as you say). But you also need an actual system that is simple in the first place. Consider the refrigerator example on page 14.
13ren
Tell me what you think of the lego motorbike on p.82-6. It focuses on affordances and constraints as communicating to the user - but the constraints must also actually exist as constraints, and not just communicate them.
13ren
I don't have it handy, so I'll reflect on Lego: There is only one destination, though. Limiting the choices available helps the child make *one thing*. It's only limited by the builder's intention and materially, by the manufacturer's marketing for that intention.
Axeman
(cont) The motorcycle has to be neat enough that it becomes the object of enough children. And then because they want to make *that*, the limitations in the piece make it clear what does and doesn't make the one thing.
Axeman
(cont)Limitation does make it clear, in this case, but clear as to a fixed goal. Choices are limited, because the overall choice is limited to one object in the first place. I don't think Lego could accomplish a set of blocks that make it instantly clear what can be made of a set of bricks.
Axeman
You said I had DoET a little backwards, so I offered that passage in support of my view. You're right that design often comes after marketing and product development - but it can also come before if usability is a primary goal. I'm just saying the obvious: a simpler thing is easier to use.
13ren
(cont) an example is the iPod shuffle. MP3 players previously competed by adding features.
13ren
(cont) "In anything at all, perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away, when a body has been stripped down to its nakedness" p.42, "Wind, Sand and Stars", Antoine de Saint-Exupery
13ren
(conclusion) If the choices are known to the designer, it's good design to signal them; but if the choices aren't yet decided (as in exploratory prototyping), it's a waste of time. Leaving off this guidance is unpolished code - which is fine, since it actually isn't finished.
13ren
+2  A: 

From what I've seen, LISP is about as flexible as a language can get. But it gets cryptic more than "messy". The endless lists of lists is powerful and flexible, but the repetition of pattern can result in confusion from everything looking the same.

Axeman
13ren
+1  A: 

Flexibility in my mind is best exemplified by the low coupling/high cohesion paradigm. Good flexible code is as orderly and simple as possible. You know you're refactoring well when you reduce disorder and increase simplicity.

le dorfier