8 ms·
Beautiful Apps, Ugly Code
- djacobs 15y ago> 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 15y agoUs 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 15y agoIn 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 http://www.infoq.com/presentations/Simple-Made-Easy
- palish 15y agoFor 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/ 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 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.
- bengl3rt 15y agoThat was an incredible read. Thanks for the link.
- ntoshev 15y agoOOP 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 https://gist.github.com/1218360 (and discussion http://groups.google.com/group/golang-nuts/browse_thread/thread/c47af0b94e5f9539 http://groups.google.com/group/golang-nuts/browse_thread/thr... )
- silentbicycle 15y agoNo, the whole point is that they're getting major speed-ups by using arrays and a giant ring buffer to avoid object allocation, reduce garbage collection, and improve cache behavior. They're writing really tightly optimized C. In Java.
- deleted 15y ago[deleted]
- drostie 15y agoI will say this: the reason I keep coming back from Lisp (and PHP) to JavaScript and Python is that I can do lots of functional things in the latter, but I get that gosh-darn useful little dot operator. Here are some of the many ways to silence someone who is shouting in all caps: (string-downcase yelling) ; common lisp strtolower($yelling) // php yelling.toLowerCase() // js lc($yelling) # perl yelling.lower() # python I don't really care about the parens and where they are, but the fact that clisp, php, and perl put this function in the global namespace bugs me to no end. It's a function which only makes sense when you have a string. Other things don't have the sort of "case" such that you could "lowercase" them.
- mattdeboard 15y agoCurious then how you feel about `sorted()` being in the global namespace. Point being I don't understand how `.lower()` et al., being class methods instead of global functions is an argument for the greatness of Python. Ultimately the responsibility is yours for invoking it in the right place with the right type of object, no matter where you put the parens.
- drostie 15y agoIn Python that actually makes some amount of sense, because Python ships with something like five different sorts of lists (arrays, lists, tuples, OrderedDicts, generators) and it's a reasonably generic idea. And it helps that for the one 'proper' case of this, lists, Python also supports list.sort(), so that "it is where it's supposed to be" as well. The only problem with list.sort() in Python is that it's void; it returns None. It should either return self (and sort self) or return a sorted copy of self, like sorted() will. On the other hand, in JavaScript, array.sort() is known to be a little broken: > [48, 19, 7, 14, 30, 22, 45].sort() [14, 19, 22, 30, 45, 48, 7] Wat. It's true that I have ultimate responsibility for my code, and it's true that in Python I can write: x = 3 x.lower() ...and unlike Java it will not complain that it has no idea how to "lower" 3. But it still suggests like Java suggests that we're going to narrow down the wildly branching tree of possibilities. When I'm sitting in the Python REPL, and I have an object x, I'll often just call dir(x), to see what I can do with it. What would Common Lisp tell me? It would kindly tell me that it's syntactically valid to call (string-downcase 3), even though that will produce a noisy error. So if I wanted to list all of the things I can do with 3, we would be here for a long time.
- bwarp 15y agoThis actually only happens if you don't think first or don't understand ever. Thinking and understanding takes more time than programming in my experience. The same level of fail will accumulate regardless of the language if you don't know what you are doing.
- josephcooney 15y agoThe real world makes code ugly. Network connections that fail part-way through sending. Other people's code that you have to integrate with that block for long periods under certain circumstances. Special cases in business logic that spoil the beautiful abstractions you'd created. Customers with funny characters in their names. Hard drives that fail. 'needs to provide business value' is another one of those pesky real-world requirements that just keeps popping up. Complaining that this gets in the way of beautiful software is akin to transit system operators complaining about passengers interfering with the running of their trains/buses/whatever.
- adeelk 15y ago“Real-world requirements” don’t make code ugly. They just make the problem harder. Hard problems still have elegant solutions.
- aufreak3 15y agoI find code that does more with less to be beautiful and valuable. Note that I'm not including code that does the same with less. As with all things beautiful, this is tough to illustrate, so I'll use an analogy here from Physics. Page "xv" of the book "A course in mathematics for students of physics" (volume 2) by Paul Bamberg and Shlomo Sternberg contains a table (do a "look inside" on Amazon) that shows four forms of Maxwell's equations as they evolved over the course of history. Each change expresses what the previous one did, but contributes something more that wasn't possible or was very difficult to express with the earlier form. The first form explicitly uses cartesian coordinates. The second form is the vector notation, which helps conceptualize and work with the equations in any coordinate system appropriate to the problem (circular, cylindrical, etc.). The third is the relativistic "four-vector" formulation, which, though it says the same thing as the vector form, allows extension to curved spacetime. The fourth exterior calculus formulation (which just reads "dF = 0, and 𝛿F = J"!) covers all of the preceding ones, and also takes exactly the same form when applied to circuits (the kirchoff laws, etc.) which is not something you can say for the preceding forms, while allowing generalization to higher dimensions. The mathematical machinery invented for that simple looking expression is incredibly beautiful (and that book does a great job of its exposition, imo). This kind of beauty in software is very desirable in my opinion and we do have examples like MapReduce to show for it. Yes the machinery to do a distributed MapReduce is complex, but the concept is simple and flexible and has a bit of the "exterior calculus" fragrance to it. Another is continuations in Scheme - which not only can replace any "mundane" control transfer (break, continue, throw, catch, and ilk) but can do much more than just those and lets you roll your own too. I believe that is the kind of beauty we should be striving for in software, not just the "pretty code" kind. (minor edits)
- georgieporgie 15y agoWhat 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 15y agoMaintenance 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 15y agoMaintenance 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.
- 15y ago
- Confusion 15y agoPick 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 15y agoI 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/ 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.
- singular 15y agoI 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.
- tikhonj 15y agoThis 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.
- gaius 15y agoRead a few of the other articles on his site for context.
- batista 15y agoNext 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 15y ago
- stiff 15y agoFor 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.
- akg 15y agoJust 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.
- akg 15y agoJust 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.
- akg 15y agoJust 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.
- mkilling 15y agoI 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.
- Jach 15y agoPerhaps 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.)
- adsr 15y ago
- gbvb 15y agoIMO, 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.