Showing posts with label design. Show all posts
Showing posts with label design. Show all posts

Friday, 31 December 2021

Beauty in Software Design (and Programming)


Recently I repeatedly heard the phrases "beautiful"/"not beautiful" used to describe and grade a software design, a bugfix or a piece of existing code. 

Personally, I didn't like it at all, as it seemed somehow simplistic, but I told to myself: be humble, don't judge, think before you speak... But then, out of the blue, I realized what disturbed me in that phrase! Let me present my argument.


Why didn't I like the "beauty" argument? Because it's too easy, too general and too unspecific.

Software design, as an engineering effort, is an exercise in finding a right tradeoff between different vectors: performance and code readability, code quality and release deadlines, programming effort and importance of a feature, etc.

When we just fall back to the "beauty" criterion we are just ignoring the more complex reality behind our code. I'd even go as far and venture to say that it is the sign of an immature engineer.  

Let me cite from a blog post*:
"- Mature engineers make their trade-offs explicit when making judgements and decisions."
Let me state it again - we are not looking for beauty when programming, we are trying to survive in a world full of trade-offs. And a mature engineer won't try to ignore that and lock himself up in an ivory tower. Let me cite from a blog post again*:
"The tl;dr on trade-offs is that everyone cuts corners, in every project. Immature engineers discover them in hindsight, disgusted. Mature engineers spell them out at the onset of a project, accept them and recognize them as part of good engineering."
Mammoths?

Have you seen the mammoths? They weren't beautiful**, not by any means! But they were optimally adapted to thrive in their habitat!


Here is one - should we reject him in a code review because of his ugliness?

Mathematics?

What about beauty in mathematics***? Isn't programming "mathematics done with other means"

Isn't there the old adage among mathematicians that "if it's not beautiful, than it's probably wrong"? And des it apply to programming (via the argument that programming is "mathematics done with other means")? 

Well, my response to that is: mathematics is pure, in the sense that it doesn't have to make any trade-offs. 

Besides, I can't see much beauty in modern advanced mathematic like, for example, in proof of Fermat's theorem. Rather than that, it's just complicated and tedious. Looks like the low hanging fruit in mathematics has been already picked... 

Update

A recent tweet expresses much the same sentiment:
--

** as a classic polish rock songtext stated (credits where credit is due!)

*** "there is some cold beauty in mathematics..." as Bertrand Russel said


Sunday, 30 March 2008

QString conversions, bugs, suprises and design

1. A trivial bug


It started with a pretty simple piece of code:
    QDomElement e = n.toElement(); // try to convert the node to an element.

if(!e.isNull())
{
TRACE(e.tagName());
....
where TRACE() sends an object to cout. The simplest of tasks you'd say, but it didn't compile. What? What year is it now? Are we in the early nineties? Doesn't library writers know about the standard library? It turned out, they know, so I used a slightly modified code:
    TRACE(e.tagName().toStdString());
but it always crashed with Qt 4.3, Windows XP and VisualStudio 2005! Why? No time to check. After some reading of Qt docs, I settled with:
    TRACE(e.tagName().toAscii().data());
It worked, but the code looked extremely ugly! After copy-pasting it for n times (no, n wasn't equal to 3, as it should be according to the Agile gospel, sorry!!!) I longed for something more elegant and explicit, something like:
    TRACE(qstr_cast<const char*>(e.tagName()))
The code was quickly written and worked instantly:
    // helper for QString conversions
// -- cute and explicit syntax: qstr_cast<const char*>()!


template <class T>
inline T qstr_cast(const QString& s)
{
return T(s.toAscii().data());
}

inline const char* qstr_cast(const QString& s)
{
return s.toAscii().data();
}
Well, it worked on my machine but not in the target environment, and only in one (different) case. Why? Of course I blamed the Qt runtime support, which already let me down with the toStdString() function. Then I saw the same effect in the debugger on my developemnt machine, and I blamed it on multithreading: there must be something wrong with locking when accessing this particular QString instance. But at last I found time to remove this bug, and looked at the sychronisation, and it was 100% correct. The bug was hidden elsewhere. The new (correct) code is:
    // helper for QString conversions
// -- cute and explicit syntax: qstr_cast<const char*>()!


template <class T>
inline QByteArray qstr_cast(const QString& s, T* t = 0)
{
// the QByteArray's const char* conversion op. will be applied
// by the compiler in the const char* context!

return s.toAscii();
}
As you can see, the old code returned a pointer to the data of a temporary instance of an QByteArray object, and of course it pointed into the void. End of story!

2. Discussion of the trivial bug


Why am I writing this? Isn't it just a banal and stupid bug? I don't think so. I think there are some points to be made.

The first one is that Qt doesn't follow the "Principle of Least Surprise"*. This code should work out of the box: cout << qtStringObj;! Why? Because C++ programmers wrote code like this for centuries! Well, almost. In the "freedom languages" ;-)** you are accustomed to writing just: print someObj; and it always works. Mind I didn't use the word "modern languages" but in modern times we expect it just to be working!

The second one is that Qt doesn't want me to use the standard library! There is no shift operator for std::ostream instead they want me to use their QStream class, which I don't know and have no desire to learn. As for QString class the Qt partisans maintain that it is vastly superior to the std::string class, but what about cout? It is somehow an C++ keyword by now. And besides, why aren't I allowed to make my own choices, even if I choose to use an inferior alternative? Maybe I don't have to be that fast in my prototype implementation? You somehow feel trapped in the proprietary Qt world, and somehow cast back in time. Is that the backwards compatibility with the original 80-ties or 90-ties design? It feels somehow frumpy.

The third one is that once I decided on the syntactic appearance of the function (i.e. qstr_cast<>() and not for example cstr_ptr() or CSTR()), I subconsciously settled on a implementation: just get the internal string data and export the pointer to the char buffer. Alas, in case of the QString this doesn't work that way! Moreover, it's not only the name choice alone, this code would work with all the string classes I knew in my long C++ life. So maybe I chose the name because subconsciously I already knew how to implement it? We pride ourself on being rational beings, but we don't know how much we depend on subconscious shortcuts, which will normally work in a familiar territory, but fail when trying something new. Just like me, as I'm relatively new to Qt.

And what's the moral? It's elementary dear Watson: RTFM first! Or perhaps: don't mix Qt and STL???

---
* for example "Applying the Rule of Least Surprise" from "The Art of Unix Programming" by E.S. Raymond: http://www.faqs.org/docs/artu/ch11s01.html or "Principle of Least Astonishment" at Portland Pattern Repository: http://c2.com/cgi/wiki?PrincipleOfLeastAstonishment

** I thought it was a joke, but not, it's an essay by Kevin Barnes: http://codecraft.info/index.php/archives/20/


Tuesday, 18 September 2007

shell as a "modern" programming language

I don't know how many of you is reading ACCU's Overload magazine, but I do. In the last issue* we find an article by Thomas Guest with the flippant title: "He Sells Shell Scripts to Intersect Sets", which demonstrates implementation of the set-theoretic operations (i.e. union, intersection, difference, etc) in UNIX shell language. As interesting pastime as these techniques are, I was struck by the final words of the article:

"For me, it’s not just what the shell tools can do, it’s the example they set. Look again at some of the recipes presented in this article and you’ll see container operations without explicit loops. You’ll see flexible and generic algorithms. You’ll see functional programming. You’ll see programs which can parallel-process data without a thread or a mutex in sight; no chance of shared memory corruption or race conditions here.

.....the intent shines through as clearly as ever: we have a compact suite of orthogonal tools, each with its own responsibility, which cooperate using simple interfaces. We would do well to emulate this model in our own software designs."
The second part of the quote was not new to me: in fact I've heard it uncountable many (pun intended) times from my distinguished colleague Stefan Z.: set of simple tools which can be combined through a common set of interfaces. You've probably read this many times before. But what I really liked, is the mercilessly pointed out (timeless?) modernity of the shell: its high level programming model, its extensibility, its focusing on concepts and hiding details away. This stands in sharp contrast to the self proclaimed "modern" programming languages of the day: Java and C# (or is this already Python?), which rather don't excel in this regard.

We so often take these features of shell programming for granted, that it must be sometimes pointed out to make us think about it. These features are not given as a matter of course! After day-to-day struggle with low level aspects of the "modern" programming languages it sometimes pays off to turn your attention to shell and contemplate it's design once again (and dream of a better world ;-).

If you are Java programmer, read it. You'll like it. It's a lesson in abstraction.

---
* Overload 80, August 2007: http://accu.org/index.php/journals/c78/


Reprise:


Speaking about set operations: relational databases are built around a set algebra (as relations are sets in, ehm, set theory), so the shell file operations could be in principle implemented with SQL! If only we could turn the Apache logs from the abovementioned article into realtional tables...

Well, why not! Look at SQLite - an embeddable, zero-configuration, C-language database (it's used in GoogleGears for example). This database has one pretty nifty feature known as "virtual tables"*: you only have to write some callback methods which will be called by the SQLite database engine to read data which are stored in different formats. For example you could treat the filesystem as a database table(s), i.e. fire SQL queries on it! So, leaving out all technical details, it's possible with the help of virtual tables to scan our logfile using SQL! I guess it's rather cool.

---
* http://www.sqlite.org/cvstrac/wiki?p=VirtualTables

Thursday, 19 July 2007

C++ sucks

Ok, I admit it's rather an unusual title for the first entry in a blog that's supposed to go against the fads of the hour! Well, let me explain...

Lately, I was porting some code of a major telco player from their standard hardware to a new board. Admittedly, it was embedded code, but nothing hard-realtime an no microcontroller stuff - just a totally regular PowerPC processor.

And this code sucks! It's not even that bad, it's object-oriented and has some separation of concerns, but it's all huge classes, huge methods, no efforts to avoid repetitive code, lot of copy and paste: you'd think it's Java, with its lack of possibilities to invent some clever shortcuts and hide the boilerplate code! This was C++ code and it sucked: ergo does C++ suck? I've never thought I'd say that, but really, I've never been so annoyed with C++ code (or any other code for that matter). Everything was so straightforward, so a=b+c, so "churn out the LOCs", so unimaginative! It reminded me somehow of bloated, repetitive and vacuous Java syntax, but it was much worse than any bad Java code I've seen. I could just see missed design opportunuties.

Sorry, it shouldn't be a rant about Java (perhaps only a little bit about its syntax...) but about C++ code. The question here is: can a programming language stop an unimaginative programmer from writing ugly code? There are two canonical answers to this question: the modern one - yes it can, through a clean language design, and the oldschool, hardcore one - no, you can write garbage in each programming language. They have both both some merit, but my private view is a different one. I think, that with a language like C++ you can always hide the ugliness away, using the proverbial "additional layer of indirection", i.e. defining some higher level abstractions instead of thinking in straightforward, procedural ways. This is the lesson that OO taught us, and a basic one! So you cannot put the blame on the programming language's design, it's rather a problem of lack of freedom in your thought, of getting bogged down with the details, of fear of simplicity. I'm not saying here that you can write garbage in each language, as some languages are better at generating it than another. I'm saying that a better language is only one half of the deal, and we must add the another half.

Ok, but there's more to it. There's another aspect to the title of this entry: do typed languages suck? And further: is iterator pattern a sign of weakness of typed languages? Are design patterns just workarounds (like J2EE patterns obviously are)? But more about it in next installments of this blog.