
Beautiful Apps, Ugly Code - 6ren
http://prog21.dadgum.com/108.html?repost
======
tikhonj
This article seems to rail against functional programming for no appreciable
reason. For one, the entire diatribe about functional purity and elegance is a
straw man: nobody claims that all purely functional code is automatically
elegant. If anything, Haskellers make fun of inelegant purely functional code;
for example, there is a command to generate point-free code nicknamed
"pointless" because everybody agrees its output is ugly.

It also complains about short examples. The reason these examples tend to be
small is because they're _examples_. If somebody posted a couple thousand
lines of Haskell, nobody except Haskell programmers (and not even most of
those) would read it, however elegant it may be. That doesn't mean longer
elegant programs don't exist!

Now, it is, of course, true that inelegant code can lead to a beautiful
product. It would be silly to claim otherwise. But that is not optimal; just
because it works, does not mean it couldn't be _better_.

Next the article claims that there is a "curious lack of concrete examples" of
purely functional programs. This is patently untrue: there are plenty of great
Haskell programs about on the internet. The canonical example is XMonad, which
is particularly apt because it is short enough to read (< 3000 lines).

In the end he presents a false dichotomy: you can either have elegant code or
focus on the product. Of course, the two are not mutually exclusive at all:
writing elegant code can help you produce a more robust program that is easier
to maintain.

~~~
batista
_Next the article claims that there is a "curious lack of concrete examples"
of purely functional programs. This is patently untrue: there are plenty of
great Haskell programs about on the internet. The canonical example is XMonad,
which is particularly apt because it is short enough to read ( < 3000 lines)._

He probably means USEFUL examples, of what one is likely to build. No one will
write an XMonad.

And XMonad is not a product -- something users want and that solves a business
need. It's a language's innards, a developer tool at the most basic.

~~~
pnathan
XMonad is a tiling window manager. It is _written_ in Haskell.

~~~
batista
Oh, my bad, was thinking he was referring to some the available built in
monads in Haskell implementation.

Still, the example is an obscure Window Manager? And a tiling one, at that?
Not the premier example of what he asks for, end user successful complicated
software that has had to mature and deal with lots of real world corner cases,
bugs etc.

~~~
tikhonj
If anything, a tiling window manager is _exactly_ that sort of software: it
has to please a wide variety of extremely picky power users and has to deal
with tons of corner cases--weird programs (the GIMP), weird layouts, multiple
monitors, panels...etc. On top of that, it has to work well with an absurd
amount of customization. The fact that XMonad manages all this with so little
code is particularly impressive.

------
djacobs
> Except that beauty rarely scales.

I completely disagree. Beauty scales best. It's almost always deadlines, and
not complexity, that uglify code. In a culture that values business value over
code extensibility and reason-ability (my word), it is very difficult to keep
a codebase clean and elegant (but it's possible). Objects make that 100x
harder.

~~~
nsxwolf
Us OO people see beauty in the object model too, not just the code.

I see everything in the world as a collection of objects and their
relationships. That may not be the only way of seeing things and may not
always be the best way of seeing things, but it is an approach my mind
naturally takes to understanding a system.

I'm down with OOP.

~~~
djacobs
In my opinion, objects conflate entirely too much and lead to tangled code,
especially if there is any data transformation involved. Have you seen Simple
Made Easy? [0]

[0] <http://www.infoq.com/presentations/Simple-Made-Easy>

~~~
palish
For an excellent example of how a clean object model can solve extremely
difficult technical problems, check out LMAX's "Disruptor" framework:
<http://code.google.com/p/disruptor/>

By rigorously separating the concerns into a clean object model, LMAX achieved
a level of performance which might correctly be labeled "miraculous".
<http://screencast.com/t/g67kFj8nRue>

It's written in pure Java. Amazingly, I haven't found anything that's achieved
better performance thus far. It's very elegant.

~~~
ntoshev
OOP has nothing to do with the Disruptor pattern achieving that performance. I
agree the concept is elegant, but I don't think the Java framework
implementing it is. You can see a less general benchmark done in far less code
in Go and C++: <https://gist.github.com/1218360> (and discussion
[http://groups.google.com/group/golang-
nuts/browse_thread/thr...](http://groups.google.com/group/golang-
nuts/browse_thread/thread/c47af0b94e5f9539) )

------
akg
Just as "the best writing is re-writing", the best coding is re-coding or re-
factoring. I find that to get things in a workable and maintainable state, I
need to write something about 3 times. The first pass is usually a hack that
fixes the problem at hand. The second pass is used for a little bit of code
cleanup and fixing corner test-cases. The third pass is the major re-factor
such that the code is maintainable and fits in with the rest of the software
design.

Having worked with extremely large code bases, I can say that the practice of
re-factoring goes a long way. The initial time investment seems excessive, but
it pays significant long-term dividends when it comes to maintenance. Not only
is it easier to extend the solution but also fix bugs and supply patches.
Furthermore, clean code (not just syntactically) is easier to understand by
others.

Writing clean code is possible, it just takes a bit of discipline to go back
and re-write. I've only worked with a handful of people who did this, but
their code was the most elegant I've seen, easy to work with, and widely
applicable.

------
singular
I notice that there seems to be this debate (as exemplified by @georgieporgie
below) between manager straw men who are only concerned with creating value
and engineer straw men who are vain + concerned with writing elegant aesthetic
code disconnected from reality.

There is a false dichotomy at play here I think - it's possible to write good
code and also provide business value, one does not imply the abandonment of
the other.

Trying to write clean, elegant code does not mean you then get to throw
business concerns out of the window, nor do business concerns mean you get to
ignore code quality.

To me, quality code is as clean + maintainable as possible while maintaining
simplicity. Some of the more 'elegant' approaches (the 1-line incorrect
haskell quicksort springs to mind) seem to somewhat sacrifice simplicity for
the power of a clever trick. I'd prefer a more verbose + arguably simpler
solution to that.

The big mistake people make here is to act as if the code doesn't have an
impact on the business value, of _course_ it does. It affects the quality of
the product in 1000 different often less-than-obvious ways. To begin with, you
have maintainability and extensibility which literally have extremely direct
affects on your ability to sustain and grow your product, but you also have
endless other factors which are a direct consequence of code quality.

For example, if your codebase starts getting really crufty and closely-
coupled, it starts to become _very_ hard to write code that doesn't fit into
the design as-is, so if the customer/business need you to do X, X might turn
out to be insanely hard, completely unintuitively to the business people. Try
explaining to them how that's related to the code.

Also, try choosing naive O(n^2) or even O(n!) algorithms when you are scaling
some software up to heavy levels of usage. The abstract non-business minded
concerns start mattering quite a bit to the business there.

------
stiff
For me, the only way to meaningfully assign meaning to a program being
"beautiful" or "elegant", is to interpret it as the program possessing a
significant part of all the possible pragmatic advantages a computer program
can possess, so on the highest level: being easy to understand, extend and
debug. Those easily translate to more concrete properties: strong
correspondence between the abstract concepts of the program and the domain it
represents, following established coding standards, making functions/methods
data-driven where possible, signalizing errors in a meaningful way an so on.
There are lots of programs with more then 200 lines that meet a very high
standard of elegance understood in this way (v8, sqlite). If your program
isn't elegant in this way, there is a real cost for both the users and the
business owners, this isn't just some abstract game. I guess you wouldn't like
to drive a car where the cables and pipes form a completely unreasonable mess,
just because it happens to work at this particular moment of time. If your
program is a mess, it will end up having more bugs, features will take longer
to develop, it will most probably work slower and so on and so forth.

The article implicitly assumes that pure functional programming contributes
positively to elegance, and since it doesn't define "elegance", it's hard to
argue with it. I don't think however there is a strong consensus in the
programming community that pure functional programming contributes positively
to "elegance" understood as defined above, and this is to me the fundamental
flaw of this article.

------
Jach
Perhaps ugliness is inevitable. Though if you can write beautiful portions of
code you should be able to get a mostly-beautiful big program by putting a lot
of beautiful portions of code together, and then the only source of potential
ugliness would be in how those pieces interact. (Which tends to be fairly
beautiful in the purely functional world and in well-designed OOP
architectures but good luck elsewhere, and good luck getting multi-language
code to interact for every set of languages (such as Python to C without using
Cython).)

But assuming that ugliness is still inevitable, I'd still rather work in a
language/environment/culture where ugliness can be kept at bay as long as
possible. If ugliness is simply a property of long programs, then obviously
languages that let you write shorter programs should scale beauty better.

Adding to the small examples, I dipped my toe into Android development tonight
while at the same time getting Clojure to run on it. Clojure let me do:

    
    
        (defmulti handle-key (fn [key] [key]))
        (let [f #(eval (symbol (str "KeyEvent/KEYCODE_" %)))]
          (doseq [[k letter] (map list (range 10) (seq "PQWERTYUIO"))]
            (defmethod handle-key [(f k)] [key] (st k))
            (defmethod handle-key [(f letter)] [key] (st k))))
        (defmethod handle-key :default [key] false) ; don't handle event
    

(Which may not even be the most idiomatic Clojure-way since I'm still
reasonably new to that as well.)

And the first-thought for how I would do that in Java:

    
    
        private boolean handle_key(int key) {
          switch(key) {
            case KeyEvent.KEYCODE_0:
            case KeyEvent.KEYCODE_P: return st(key); break;
            case KeyEvent.KEYCODE_1:
            case KeyEvent.KEYCODE_Q: return st(key); break;
            case KeyEvent.KEYCODE_2:
            case KeyEvent.KEYCODE_W: return st(key); break;
            case KeyEvent.KEYCODE_3:
            case KeyEvent.KEYCODE_E: return st(key); break;
            case KeyEvent.KEYCODE_4:
            case KeyEvent.KEYCODE_R: return st(key); break;
            case KeyEvent.KEYCODE_5:
            case KeyEvent.KEYCODE_T: return st(key); break;
            case KeyEvent.KEYCODE_6:
            case KeyEvent.KEYCODE_Y: return st(key); break;
            case KeyEvent.KEYCODE_7:
            case KeyEvent.KEYCODE_U: return st(key); break;
            case KeyEvent.KEYCODE_8:
            case KeyEvent.KEYCODE_I: return st(key); break;
            case KeyEvent.KEYCODE_9:
            case KeyEvent.KEYCODE_O: return st(key); break;
            default: return false; break;
          }
        }
    

It's not immediately clear to me how I could make that Java code shorter, and
it's not exactly very pretty. (Long switch statements rarely are in my
opinion.) (After some thought, I suppose you could use Java's reflection
capabilities.)

~~~
jrmg
I'm confused as to why your switch is so complicated. Aren't all the cases but
the default one actually the same? Why is the "return st(key); break;"
repeated?

Anyway, it's a while since I've done any Java programming, so I might have my
regex syntax incorrect, but:

    
    
        private boolean handle_key(int key) {
            if(KeyEvent.keyCodeToString(key).matches("KEYCODE_[0-9QWERTYUIOP]") {
                return st(key);
            }
            return false;
        }
    

is a lot shorter and easier to read than the Clojure code. It's not as
efficient as the Java code you have, of course, but I suspect the Clojure code
ends up rather inefficient too with all the taking apart and gluing together
of symbol names.

~~~
tensor
The Clojure code isn't particularly well written, and given the apparent goal
here using a regex is hugely overkill. Despite being inelegant, enumerating
all the valid keycodes to a set is the most appropriate answer.

Clojure provides a constant time dispatch function called case that is perfect
for this.

------
gbvb
IMO, many times the ugliness of the code reflects the brittle thinking of the
architect/designer of the application.

If it is designed to expect [rigid design] certain set of rules and not
accomodate new and changing requirements, or if it is designed to accomodate
unknown/made up requirements [overengineered design], you can endup with code
that looks either like a framework for frameworks' sake or a perfect code for
the set of known requirements.

In both cases, you endup either with code that cannot easily be modified
[rigid] or you endup taking many shortcuts because the original design did
(and could) not anticipate new requirements [overenginnered].

One of the ways to circumvent it is to know what you need design and what you
need to be able to extend without modifying the original intent.

------
mkilling
I found that things which the end user will consider as 'beautiful' or
'natural' are very hard to express in simple, elegant code. The way our
computers work just does not lend itself to creating stuff that looks
naturally to a human being.

Take an iPhone app for example: The simple, elegant way to implement a page
change in the Mail app would be a to just switch the page like this:

    
    
      currentPage = pages[nextIndex];
    

However, when you look at the actual app, you see that it's not just a simple
flip. The previous page flies out to the left, the next page flies in, there
are at least 5 different tweenings happening to move the navigation buttons,
page title, etc.

The resulting code is probably far from what you would call simple and
elegant.

------
Confusion

      Pick any program outside of the homework range, any
      program of 200+ lines, and it's not going to meet a 
      standard of elegance.
    

That's because, as the article says, smallness is usually presented as an
integral part of elegant code. 200+ lines of code is not small, by definition.
200+ lines of code also look like a jumble in any language (especially when
you don't know the language well, but even when you know the language very
well), unless you take the time to pick it apart and understand it.

I think the main problem is this: you can't judge elegance from merely
glancing over code. You have to understand it to judge whether it is
beautiful. Unfortunately, because all those small examples have primed us to
equate smallness with beauty, any larger program is swiftly deemed ugly. This
takes conscious effort to overcome: large programs can be beautiful, but they
require you to discover the beauty. It won't be obvious.

~~~
drostie
I don't know. I composed a lazy-evaluation library called promises.js for an
ongoing project which I call "ducky":

<https://github.com/drostie/ducky/>

Now, it's 277 SLOC by GitHub's reckoning, and it solves a really esoteric
problem (providing a language to wrap asynchronous calls in, so that you can
write asynchronous code synchronously). The problem wouldn't even be
particularly clear if you didn't know something about Node.js and how the API
is set up. In that sense maybe you're right.

But there's another sense in which you're maybe not right. Perhaps this is
just me feeling too much fatherly affection for my code, but I like to think
that even if you were baffled by the expected use of the project, you could
still break promises.js into chunks (in fact, in the source it's done for you)
which helps you understand what each of these methods is, and what it does,
and how to use it to accomplish some bizarre and esoteric goal.

So it's semantically ugly in a deep sense -- "what the hell is this doing, why
am I reading this code" -- if you do not take the time to 'discover the
beauty', to use your words.

But it's semantically pretty in a much less profound sense; the code is pretty
straightforward to look through, and the higher-order ideas are built straight
out of lower-order ideas, which are clearly partitioned off, and so forth.

------
georgieporgie
What matters in software: creating value, shipping product to the end user.
What matters to software engineers: just about everything else, for some
reason.

Oddly, when I _talk_ to software engineers, _most_ of them just want to create
value, ship product, and (hopefully) come out with a readable codebase. Yet
when I read blogs or HN, everyone seems to be obsessing over the "value" of
exotic languages, 'new' paradigms (usually rediscovered), obscure algorithms,
and sample code so simplistic or contrived as to be meaningless. The one
constant is that whatever was done ten years ago was pure folly, but luckily
we know better now.

~~~
tikhonj
Maintenance and robustness matter as well. You not only need to deliver a
product, but you need to ensure that it does what it's supposed to as much as
possible, and fix it if it breaks.

Most products--particularly successful ones--take a lot more effort to
maintain (both in fixing bugs and adding features long after the initial
release) than to publish. You want to minimize the amount of effort needed to
add features or fix bugs; a good way to do this is to have shorter, more
elegant code.

Additionally, plenty of fields require very robust code. If all you do is
share pictures of cats a server held together by Perl and prayer is more than
good enough. If you're investing large sums of money based on computer models,
you better not have any errors. There is a reason why banks are some of the
main users of Haskell.

So: releasing is only part of the game. In certain cases valuing a faster
turn-around is more important than code quality; in others, the cost of
maintaining a poor code base far outlives the advantage of releasing early.

However, there is another problem with this article: elegant code and
"focusing on the application" are not mutually exclusive. If anything, writing
elegant code will probably save you time, let you reuse more code and spend
less time debugging.

~~~
georgieporgie
_Maintenance and robustness matter as well._

Hence, readable codebase. Everything else you mention is part of creating
value. Code that crashes or breaks regularly does not provide significant
value.

