In order to handle complexity, I split my program carefully into
modules with low coupling. I'm happy until a new feature comes along
which cannot be implemented without destroying the independence of the
existing modules. Then there are two options: Either I redesign the
system based on the new requirements or hack the new feature into the
old architecture. Both alternatives make me sad.
I recognize some repetitive pattern in my code. So I create some
straightforward abstraction that removes the redundancy. I'm happy
until the pattern appears again, but this time in some slightly
different form so that the old abstraction can't handle it. Then I
change the abstraction to include the new case. Eventually, I realize
that the abstraction has only complicated the system, because now one
has to understand both the implementation of the abstraction and the
use of it. This make me sad.
I'm working on some easy task. I know that I have solved problems like
this many times before. Unfortunately this time my brain just refuses
to solve the problem. Without solving the problem, I go home and think
about pursuing a different career.
On the next morning I solve the problem within 5 minutes and I'm glad
to be a programmer.
A good night's sleep is an essential part of my problem solving work flow. For smaller problems, I find a brisk walk can often break the mental logjam.
> Eventually, I realize that the abstraction has only complicated the system, because now one has to understand both the implementation of the abstraction and the use of it.
That's a real problem. The best abstractions are so useful, that they even help understanding when you only use them once.
E.g. functions come to my mind --- because they have clear entry and exit points (of control and communication), they can be easier to understand than the same code inlined.
(Functors and Monads also seem to enhance my understanding.)
Functions are indeed a good tool but at one point, they accumulate more and more parameters or more complex structures as parameters. Eventually, while you still understand the purpose of a function, it is difficult to understand all that it abstracts away.
I am just saying this because I totally relate to the article,it is often very hard to contain or abstract complexity. or it requires nearly equally complex mechanisms or abstractions.
Yes, a function should be short and sweet. This applies very well to functions in Haskell or Scheme (or words in Forth). Those languages (Scheme slightly less so) also have low syntactic overhead for defining a function/word.
It's OK for functions to grow more abstract. Just provide the simple things, too.
E.g. you start out with `map' and `filter', but then realize that `foldr' can express both of them and much more. One viable path is to rewrite the definitions of `map' and `filter' in terms of `foldr'. One should not use `foldr' everytime in the code where a `map' or `filter' suffice.
One can layer abstractions and then use the most specific tool possible that still has all the flexibility you need at each point.
As features accumulate over time, I find this is harder and harder to maintain. But it heavily depends on the situation (abstractions don't always hold as well as originally hoped). I guess I just remember more the times it goes wrong than the times it went right.
Oh, I often have to bang my head against the wall quite a few times, before I hit on the right abstraction.
Having more powerful abstraction techniques, like they are possible in languages like Haskell or Scheme/Lisp, tends to give less leaky abstractions for me.
I've been learning functional programming over the last few months and I really like it. I can't help but notice, however, that there is a difference in "beauty" as it applies to fp and oop.
A good piece of OOP code is verbose yet simple. It may be a world of nouns, but there's a little tiny chunk of code in there that does one thing and does it well. Beautiful OOP is made for morons. And I mean that as a compliment.
FP, on the other hand, couples the data structures so tightly in with the code that I'm always the human linker -- keeping track of dozens of different things at the same time. But when done well, all of this incredible amount of work just falls out of a line or two of code. You have to keep track of the pieces, but your opportunity to combine them in cool ways is incredibly higher. Beautiful FP is like watching origami -- from a simple base somehow something beautifully complex emerges.
Then there's the difference between beautiful startup code -- you have no idea of the problem so it is able to pivot in multiple directions quickly, and beautiful commercial code -- you know the problem and you've met the solution in the most maintainable, understandable way possible.
The term "beautiful" is overloaded too many times. And good coders aren't looking for artistic perfection. I agree with the author: it's a bad idea.
"I can't help but notice, however, that there is a difference in "beauty" as it applies to fp and oop."
I don't know that I agree with this. Code is code. Now, I will agree that you may have to take a different approach to writing beautiful code in OOP vs FP. But I don't think that what makes functional code beautiful is different than what makes object-oriented code beautiful.
In all, I feel that beautiful code is (in this order):
* Something that gets the job done.
* Something that gets the job done correctly (there is a difference).
* Something that tackles a complex problem in an easy-to-understand way.
I agree that we as programmers shouldn't seek perfection when no perfect solution exists. However, it's usually in your best interests in the long term to put forth the extra effort to make your solution beautiful where it's feasible.
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
...there are no general principles or techniques of software design. I hope that someday we will discover such principles.
Many of us already have.
The lack of a Universal Theory of Software Design != "no general principles or techniques".
Anyone programming today is standing on the shoulders of giants. Their trial and error is our starting point. No, they didn't figure everything out, but it they had, programs would be written by other computers, not us. Maybe someday, but I, for one, am not quite ready for that.
Maybe this is an argument about what "beauty" means. The "alternative" presented in the text of "straightforwardness and conventionality" are examples of what makes code beautiful, in my opinion.
That is the problem with these vague terms that are used to describe poorly defined virtues, isn't it? Everyone can take whatever meaning they choose out of a term like "beautiful code." For you, I suspect "beauty" is nearly synonymous with "good". For the author, I think "beauty" was closer to "elegance." But there I go, defining one vague term with another.
You hit the nail on the head. Generally, it's counterproductive to allow emotional valence to overshadow the cold meaning of the words. For me, a statement like "this code is beautiful but unmaintainable" sounds perfectly fine and meaningful. But many people seem to have a vague cluster of "good" words, and when they're told that a certain "good" thing doesn't have another "good" attribute, cognitive dissonance ensues and they start making stuff up.
Beauty is not a sufficient basis for a happy marriage.
But every happy marriage is founded on that each finds the other beautiful.
If you at least get the foundation of the system "beautiful", when the abstractions start to leak, as they invariably will, you can manage them to a much greater extent. You're right about not expecting the simplicity and elegance to remain, but there's a huge difference in manageability toward the end of each system's life. Get the foundation right; the house will follow.
But every happy marriage is founded on that each finds the other beautiful.
Really? That doesn't accord with my experience, or any actual research that I've read. The conclusion of the research that I've read is that appearance generally matters more to men, and less to women. And anecdotal experience suggests that the initial infatuation fades in perhaps 7 years or so, and after that looks don't help that much.
Not that you're likely to believe me. After all I've only been married 20 years, so what could I know?
I think you misunderstood that sentence. Beauty doesn't necessarily mean appearance, especially for aphorisms and poetry. If you're marrying someone because they're kind and caring, that's the thing about them that you're finding beautiful.
And this confusion only goes to prove the original point, about beauty being illdefined.
What's sad is that he refused the project rather than express his viewpoint.
If his point was that beautiful code is rare and only refined through a long iterative process perhaps he could've found some code that was initially average or even bad and showed what each incremental refactor/rewrite did to it until it became "beautiful".
I don't think it's sad. He felt that the project put forward a way of thinking he disagreed with - I too disagree with that way of thinking.
My last boss told me "there are programmers who produce code with absolutely no bugs or problems". I would prefer no to give that kind of misconception any more ammunition.
In the larger sense beautiful code may not be a very useful concept, but when it comes to a tangible piece of code, the actual lines in front of you on the monitor, there is definitely a difference between ugly and beautiful code. No question about it. And over 80% of the time the prettier code is the better one.
When he says that he has learned to distrust beauty, what he should be saying is to distrust infatuation. It is infatuation that blinds us to the limitations of code (or anything else). You can get past the infatuation and beautiful code will still be beautiful -- even though you can see its limitations.
I wonder how much of the tension the author alludes to between code and beauty is due to the connotation that beauty is somehow uncompromising. I too share a certain discomfort with that notion, largely because most projects evolve, which makes full understanding impossible and compromise necessary. That said, I think there's a certain beauty in a good trade-off and perhaps us programmers could benefit from celebrating that.
In order to handle complexity, I split my program carefully into modules with low coupling. I'm happy until a new feature comes along which cannot be implemented without destroying the independence of the existing modules. Then there are two options: Either I redesign the system based on the new requirements or hack the new feature into the old architecture. Both alternatives make me sad.
I recognize some repetitive pattern in my code. So I create some straightforward abstraction that removes the redundancy. I'm happy until the pattern appears again, but this time in some slightly different form so that the old abstraction can't handle it. Then I change the abstraction to include the new case. Eventually, I realize that the abstraction has only complicated the system, because now one has to understand both the implementation of the abstraction and the use of it. This make me sad.
I'm working on some easy task. I know that I have solved problems like this many times before. Unfortunately this time my brain just refuses to solve the problem. Without solving the problem, I go home and think about pursuing a different career.
On the next morning I solve the problem within 5 minutes and I'm glad to be a programmer.