Monday, April 13, 2009

A Plethora of Paradigms

paradigm - a pretentious and overused term for a way of thinking (Bjarne Stroustrup, "C++ Glossary")

C++ is frequently described (by Stroustrup and others) as a multi-paradigm programming language, which simply means that it lets you use more than one programming paradigm (such as procedural or object-oriented) and freely switch between or combine paradigms. When I was first introduced to this concept of multi-paradigm programming languages, I thought that it was an incredibly cool trait of C++. Then I read on Wikipedia that the multi-paradigm label can be applied to many more languages than I'd realized and that C++ is a relative lightweight at only three paradigms. (The current record holder, supporting eight Wikipedian-recognized paradigms, is a language I've never heard of called Oz.) However, C++ is probably still the best known example of this sort - there are even ">books about it - and has some of the most practical multi-paradigm applications. A quick overview of C++'s paradigms:

First, of course, is the structural or procedural programming paradigm. Procedural programming's pretty passé, so there's not much to be said here, except to note that C++'s multi-paradigm nature lets you incrementally migrate a procedural app to an OO design.

Second is object-oriented programming. When OO first went mainstream, it was billed as a way of achieving reuse in software development. This doesn't seem to have quite worked out as advertised; creating truly reusable software is hard regardless of the technology you're using, and while OO design is critical to frameworks from MFC to RoR, framework development isn't where most programmers live. Even if its ability to grant reuse per se is limited, modern OO design is quite good at permitting software to be flexible, extensible, and stable in the face of change. "Modern OO design" means taking full advantage of guidelines like the SOLID principles and the Law of Demeter rather than just applying the basic OO techniques of encapsulation, inheritance, and polymorphism. It also includes knowing when to bend those guidelines if circumstances call for it.

C++'s third paradigm is generic programming. I find this to be harder to get a handle on... It's much less commonly taught than object-oriented programming, and although I've worked in C++ for years, it's only been in the last year or two that I've really started to "get" generic programming. (A good test to see how well you understand generic programming is to browse the Boost libraries and see how many of them you think you could, in theory, implement.) Generic programming makes the STL possible; it makes it possible to add features like dimensional analysis, lambda expressions, and callbacks to C++; it's a major component in adding user-defined types of all sorts. It also creates some rather heinous error messages...

However, generic programming isn't just for STL containers and the metaprogramming wizards at Boost; it overlaps with and can partially replace OO techniques. OO programming is known for permitting software components to be loosely coupled (such that they can be freely combined and freely changed, within limits, without the effects of those changes rippling out to other components), through polymorphism and interfaces. Generic programming also permits loose coupling, except that it's handled entirely at compile time, with no runtime overhead, and it doesn't require that objects be related through an inheritance hierarchy. The Curiously Recurring Template Pattern is probably the simplest and best known example of this (and is even referred to as compile-time or static polymorphism), but it's far from the only example. Most of part four, for example, of Imperfect C++ contains different applications of generic programming techniques to simulate what might require inheritance, interfaces, or (C#) extension methods in the OO world.

For even more power, the generic and OO paradigms can be combined. And because C++ is such a flexible language, you're not limited to the "officially supported" paradigms. I've already touched on Boost's support for functional programming; as another example, reflection (which is available through vendor extensions, like C++Builder's or the .NET Framework's and through third party libraries like CERN's Reflex) can also promote flexible, extensible software. Having three different techniques from three different paradigms that can be used individually or in various combinations offers incredible power: power to shoot yourself in the foot by creating an overcomplicated, obtuse rat's nest of code, or power to write robust, extensible software. Now if I could only figure out which category my code falls in...

Tuesday, April 7, 2009

Things I Didn't Know about C++: Template Template Parameters

Continuing my series on things I didn't know about C++...

I knew about templates, and I knew about parameters to templates, but I did not know about template template parameters, which are useful when dealing with containers and policies and, if nothing else, are worth learning about for having such a "pleasingly repetitious name" (in C++ author Stephen Dewhurst's words).

A template template parameter is simply a template parameter that is itself a template. Dewhurst gives the example of a Stack template that can be customized to use a particular underlying storage container:

template <typename T, template <typename> class Cont>
class Stack;

Stack<int, List> stack_of_ints;

Without template template parameters, the base type (int in this case), which hampers readability at best and could be a source of errors at worst:

template <typename T, typename Cont>
class Stack;

Stack<int, List<int> > stack_of_ints;
Stack<double, List<int> > stack_of_doubles_badly_truncated_to_ints;

Template template parameters can be used anywhere you need to let the caller customize an algorithm or class for a particular container, policy, or similar:

  • A stack or other higher-level container can be instantiated using any lower-level container, as in Dewhurst's example.
  • A class or function can be customized with a policy template. Andrei Alexandrescu gives an example of this in section 1.5 of Modern C++ Design. (His example is also summarized in this Stack Overflow post.)
  • Template template parameters can be used when you need to know both the type of a template and how that template was instantiated, although this comes up less often than you might initially think, because there are often better ways to find the types involved. For example, there's no need to declare template <typename T, template <typename> class Cont> void ProcessContainedObjects(Cont<T>& cont) to let ProcessContainedObjects know what T is when any well-written container will define the Cont::value_type typedef.

Template template parameters are a useful addition to the C++ template programming toolbox, alongside techniques such as partial template specialization, type traits, concepts (also in C++0x), and C++0x's variadic templates (which can be simulated in C++03 to some extent by using default template arguments or by using Boost Preprocessor to create overloads of every arity from 1 through a #define'd limit).

Sunday, March 15, 2009

Floating Point Exceptions

As further evidence that floating point math is stranger than you think, consider the following code:

#include <stdio.h>
#include <math.h>
#ifdef __unix__
#define _GNU_SOURCE   // gives us feenableexcept on older gcc's
#define __USE_GNU     // gives us feenableexcept on newer gcc's
#include <fenv.h>
#else
#include <float.h>
#ifndef _EM_OVERFLOW
#define _EM_OVERFLOW EM_OVERFLOW
#endif
#endif

int main(int argc, char **argv)
{
    float a, b;

#ifdef __unix__
    feenableexcept(FE_OVERFLOW);
#else
    _controlfp(0, _EM_OVERFLOW);
#endif

    // gcc will compute these expressions at compile-time (which is
    // actually kinda cool, but it ruins the example) if we just do
    // pow(10.0, 50) and fabs(-13).
    int exponent = 50;
    a = pow(10.0, exponent);

    puts("1e50 is too big for a float, but we got this far,\n"
         "so we must be okay, right?\n");

    b = -13;
    b = fabs(b);

    puts("You'll never see this, because we just got an overflow trying\n"
         "to process a really small number!  How can this be!?!?\n"
         "The world is coming to an end!\n");

    printf("%f %f\n", a, b);

    return 0;
}

By default, at least on x86 systems, floating point exceptions are usually masked 1, but I've been enabling them as part of my attempt at hairshirt programming or fail fast programming; exceptions such as overflow or division by 0 probably indicate logic errors or algorithmic limitations within the software, and I'd like to know about such problems as soon as possible. That led me to this problem: the pow call generates the exception, but it's not raised until the fabs call, completely confusing the poor programmer. Section 8.6 of the Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 1 (hereafter IASDM) explains how this behavior comes to be:

Because the integer unit and x87 FPU are separate execution units, it is possible for the processor to execute floating-point, integer, and system instructions concurrently. No special programming techniques are required to gain the advantages of concurrent execution. (Floating-point instructions are placed in the instruction stream along with the integer and system instructions.) However, concurrent execution can cause problems for floating-point exception handlers.

Intel's manual provides more details; to summarize, exceptions generated within the FPU are flagged within the FPU's status register but not raised until the CPU executes the next floating point operation. In practice, this should rarely be a problem: if the exception is generated during an operation, it will be raised when the results of the operation are written back to memory, and if the exception is generated when the results are written to memory (due to truncating to a smaller precision), it will be immediately raised as long as you immediately use the result that you just stored. So as long as you're not storing a low-precision value for later use and immediately going on to something else, you ought to avoid the confusing behavior described here.

If you do suspect that something like this is happening, then using C99's fetestexcept functions can help pinpoint the culprit:

    int exponent = 50;
    a = pow(10.0, exponent);

    if (fetestexcept(FE_OVERFLOW)) {
        puts("Warning; doom is impending.");
    }

    b = -13;
    b = fabs(b);

Or you can use C99's fegetenv and the undocumented internals of its fenv_t parameter to examine the entire x87 FPU state - its status word (including pending exceptions), its control word (including the current exception mask), and so on. If your environment doesn't provide an implementation of these fenv.h functions, you can borrow the MinGW runtime's (after adjusting the syntax for your compiler); the implementation is extremely simple (a few lines of assembly per function), and the MinGW runtime is in the public domain.

As an aside, this particular problem would not have happened if the code used doubles throughout instead of floats. Using floats at all is probably a mistake, but I've had some difficulty finding detailed information on the tradeoffs of floats versus doubles. Here's what I've been able to find so far:

  • Doubles take up twice as much memory as floats, although the extra memory usage is rarely an issue.
  • The x87 FPU always operates on 80-bit long doubles internally, as described in the IASDM (volume 1, section 8.2). For this reason, doubles are usually no slower than floats.
  • As a small exception to the previous statement, some builtin functions like pow that offer both float and double versions seem to run faster in their lower-precision versions.
  • As a larger exception to the previous statement, the performance SSE is entirely dependent on the size of the data it's operating on, so floats are twice as fast as doubles. On the other hand, if you're doing the kind of graphics, scientific, or gaming development that would most benefit from SSE, you probably already know more than I could tell you about the kinds of performance issues you'll face.
  • Floating point precision has a few historical oddities in C and C++; particularly old compilers promoted floats to doubles while doing calculations, and floats are promoted to doubles when given as arguments to variadic functions. (Since pointers can't be promoted, printf and scanf are asymmetrical: printf("%f", f); will work whether f is a float or a double, but scanf("%f", &f); requires that f be a float.)
  • The IASDM says (volume 1, section 8.2) that doubles should be used "as a general rule" but notes that "the single-precision format is useful for debugging algorithms, because rounding problems will manifest themselves more quickly in this format" and that 80-bit long doubles can be used "when an application requires the maximum range and precision of the x86 FPU."
  • If you're using Microsoft Visual C++ (as of MSVC++ 2008), you can't use 80-bit long doubles. Sorry.

If there are other pros and cons involved in selecting a precision, please let me know. Of course, if you or your predecessor designed your C/C++ code right, all of this is controlled by a single typedef, so it would be very easy to test for yourself. (If you do have the pleasure of working on such well-designed code, feel free to post a comment and gloat over those of us stuck maintaining legacy apps.)

1. Specifically, the x87 FPU disables all floating point exceptions by default, as described in section 8.1.5 of the Intel 64 and IA-32 Software Developer's Manual Volume 1. Microsoft follows suit (unless you set a compiler option), and apparently Linux does too, but CodeGear C++Builder only masks underflow, denormal operand, and inexact result exceptions as part of its startup code.

Sunday, February 22, 2009

Things I Didn't Know about C++: Local Classes

C++ is a complex language, and although I thought I knew it pretty well, I'm continuing to find areas of the language that I either didn't know or didn't understand well enough. So, on the (possibly narcissistic) assumption that others may not know enough about them too, here's a brief series of postings about them.

First up are local classes, which are not the same thing as nested classes.

class imitation_string {
public:
  // This is a nested class.  Other code can then use imitation_string::iterator.
  class iterator {
  public:
    iterator& operator++();
    iterator& operator--();
    char& operator*();
  };
};

void string_tokenizer(const imitation_string& in, std::vector<imitation_string>& out)
{
  // This is a local class.  It can only be used from within this function.
  class token {
    // Insert class definition...
  };
}

Local classes are closely related to nested (local) functions, which are not permitted in C++.

// Not valid C++.
int f()
{
  int i = 0;
  void g()
  {
    // A nested function has access to its enclosing function's variables.
    i++;
  }
  g();
  return i;
}

The position of local classes within the design of C++ seems very awkward to me. "True" nested functions require special implementation from the compiler, due to the requirement that they be able to access their enclosing functions' stack frames, and calling a nested function (via a function pointer) after its enclosing function has exited is a quick way to crash a program. This might be reason enough to omit them from the C++ language, but it didn't stop the GNU C Compiler from implementing them as an extension. Stroustrup dismisses local functions by saying that "most often, the use of a local function is a sign that a function is too large," but that didn't stop local classes from being permitted (with the same "too large" caveat). Nested functions occupy a much more comfortable position in other languages; Pascal (and therefore Object Pascal and Delphi) have supported them for ages, they're a key development technique in Javascript (used for everything from closures to jQuery event handlers), lambda expressions offer equivalent functionality in languages which support them, and so on.

Even ignoring their awkward position, local classes in C++ have some odd traits. First, they can neither be used as template parameters (so no smart pointers to local classes). Local class templates and local template methods are likewise prohibited. Second, although local class methods cannot access the enclosing function's variables (unlike a nested function), they can access static local variables within the enclosing function.

// Valid C++!
int f()
{
  static int i;
  class g {
  public:
    static void Execute() { i++; }
  };
  g::Execute();
  return i;
}

Similarly, local classes within a class method can access static members (even protected and private static members) of the enclosing class, and such access is implicitly scoped. They can also access protected and private members of friends of the enclosing class.

class F {
public:
  DoStuff();
private:
  static int i;
};

int F::DoStuff()
{
  class G {
  public:
    static void Execute() { i++; }
  };
  return i;
}

Various workarounds have been proposed for local classes' inability to access local variables of their enclosing functions. Herb Sutter gives a rundown of the various options in GotW #58. His final solution is worth mention; in an bit of C++ judo, he turns the enclosing function into a class, whose constructor contains the function body and which is implicitly convertible to the desired return value. The function's local variables become member variables, and the local functions / nested classes become class methods that can access these member variables.

Local classes have two main uses. First, they can be used to augment the regular control flow of a function. For example, Boost's new ScopeExit library lets you write arbitrary code to be executed whenever a code block exits (whether it exits by finishing, an explicit return statement, or throwing an exception). It implements this by defining a local class whose destructor executes the code RAII-fashion. The Google C++ Testing Framework offers another example of augmenting control flow with local classes. A unit testing framework needs both a way to abort the current test if a test assertion fails and (ideally) a way to signify that certain failed assertions are expected. The standard way to do this is to have failed assertions handled by throwing exceptions, and expected failures can be caught and handled. However, a design goal of Google Test is to avoid requiring the use of exceptions for maximum portability. Failed assertions are handled by a simple return statement. Expected assertions are wrapped in a local class method so that the return statement doesn't abort the entire function.

static int i = 1;
// This asserion:
ASSERT_EQ(1, i);
// expands to code vaguely resembling this:
if (1 != i) {
  ReportFailedAssertion(1, i, "ASSERT_EQ(1, i)", __LINE__);
  return;
}

// This assertion:
EXPECT_FATAL_FAILURE(ASSERT_EQ(2, i));
// expands to code vaguely resembling this.  Note the local class scoped to
// a dummy do/while block.
do {
  class GTestExpectFatalFailureHelper {
  public:
    static void Execute() {
      if (2 != i) {
        ReportFailedAssertion(2, i, "ASSERT_EQ(2, i)", __LINE__);
        return;
      }
    }
  };
  GTestExpectFatailFailureHelper::Execute();
} while(false);

Second, local classes can be used like any other class or function, as a mechanism for reusing and refactoring code. If you have code that appears in more than one place within a function, but is too specific to be used outside of that function, then following the principles of information hiding (if you prefer the dry CS term) or Spartan programming (if you prefer classical historical allusions or gratuitous Frank Miller references), you can use a local class to keep the code scoped as narrowly as possible. Personally, I've very rarely found code that's repeated within a function but is so specific that it will never be used outside of that function. However, this could simply be a case of my available tools determining how I solve problems. For example, Delphi permits local functions, and Delphi developers seem to find them moderately useful; the latest incarnation of Delphi's Visual Components Library, consisting of roughly 11,000 methods across 220,000 lines of code, uses about 190 local functions. Walter Bright, the creator of the D programming language, gives several examples of how nested functions can be used. He concludes,

Lack of nested functions is a significant deficit in the expressive power of C and C++, necessitating lengthy and error-prone workarounds. Nested functions are applicable to a wide variety of common programming situations, providing a symbolic, pointer-free, and type-safe solution.

They could be added to C and C++, but to my knowledge they are not being considered for inclusion. Nested functions and delegates are available now with D. As I get used to them, I find more and more uses for them in my own code and find it increasingly difficult to do without them in C++.

Sunday, February 15, 2009

The Problem with the Web

In "Why Chrome is Shiny," Jonathan Edwards does a wonderful job of articulating the vague misgivings that I've had about the current rush of interest in web applications:

I have realized that Internet browsers are a dead end, much like MS-DOS was... Some examples of these problems:

  1. No multithreading
  2. Reference counting GC, causing memory leaks
  3. Only 2 outgoing TCP sockets, and only to same site as URL
  4. Whole-page rendering, making dynamic layout changes unpleasant
  5. DOM incompatibilities
  6. Event firing and handling incompatibilities
  7. Limited standard libraries, and poor support for large-scale programming

All of this reminds me very much of MS-DOS. Like the browser, it was essentially a toy that was not originally intended to be used for anything serious or intense. Hackers came in and discovered they could do all sorts of things beyond those original intentions, and that they could get rich in the process. In the resulting gold rush there was no time to worry about fixing the platform. MS-DOS willfully ignored the existing body of knowledge about how to design an operating system.

Javascript has its good parts - prototypes, first-class functions, JSON - but it has plenty of problems too. People are expending prodigious effort finding solutions or workarounds for those problems - for example, the V8 and TraceMonkey teams' efforts to improve Javascript's performance - but I can't help but wonder if that effort would be better spent on a language that was, you know, designed for the purpose for which it's being used.

And Javascript is only one problematic aspect of current web development. Web security, for example, is awful; Jeremiah Grossman estimates that 90% of sites have vulnerabilities, and he and his team can generally find a vulnerability in under two minutes. (And he writes some great war stories about doing so.) Security for web development is roughly where security for network server development was twenty years ago: it's possible to write secure code, but only if you know what you're doing, and only if you never make a mistake. Twenty years ago, that meant never overflowing a buffer, never using sprintf/strcpy/strcat, and being very careful how you used the nonintuitive strncpy/strncat. Now, it means never building an SQL query from user data, always HTML-escaping text before outputting it, blocking CSRF, and so on. Development on the desktop and in network services is finally advancing from "secure if you try really hard" to "secure by default", thanks to training and awareness efforts by SANS and others, more secure (harder to misuse) C libraries such as Microsoft's and OpenBSD's, the selective replacement of C and C++ with higher-level languages that eliminate the possibility of memory allocation or buffer overrun errors, and vendors (primarily Microsoft) finally embracing the principle of least privilege. Web development has yet to make this transition from "secure if you try hard" to "secure by default." (Unless there have been advances that I'm not aware of? The most intriguing idea I've read is to use a strong type system, such as Haskell's, to distinguish between unsanitized and sanitized data.)

In spite of all of the problems with web development, it's the best (only?) method we've found of writing cross-platform, zero-deployment, sandboxed apps that can share data where needed and access data from anywhere. These are valuable features.

What should be done about the problems with web development? Silverlight might have the right idea, but I prefer my web without vendor lock-in. One approach (apparently favored by Edwards in "Why Chrome is Shiny") is to use a platform like the Google Web Toolkit or haXe to build on top of the current web platform and (hopefully) hide many of its shortcomings. At the very least, I figure we should be aware of the problems of the current technology craze and be a bit cautious of jumping on the bandwagon. That's good advice for technology in general.

Edit: I don't feel like I communicated very well... What seems strange about much of web development - what seems surreal about so much of software development in general - is that none of it has to be this way. It's not like other fields of engineering where are solutions are constrained by the laws of physics or by the chemical properties of the substances we're working with or similar; it's all, as Fred Brooks says, "castles in the air, [built] from air." It's largely an accident of history and of market forces that this combination of HTML and CSS (incompatible between vendors and often done improperly on web sites), Javascript (rushed out the door by Netscape and saddled with some bad design decisions), and XHR. And now, developers have spent prodigious effort making this concoction work, and with so much brains and sweat put into it, it often works amazingly well. But it's a place no one would choose to start from if they could have a choice.

Friday, January 30, 2009

Floods, Recessions, and Other Forces

I found out today that a friend of mine was getting laid off.

I make computers obey me for a living.  I consistently try to improve my skills, and I'd like to think that I'm pretty good at what I do.  I tend to assume that I can master any problem or challenge that comes my way.  But today was a reminder that there's still a great deal that I can't control.  Professionally, I can't control my boss, my coworkers, vendors, or customers; in the broader realm of life, I can control even less.

As Dennis Bakke said in Joy at Work, when there's a hundred-year record flood, it doesn't matter how well your house is built.

It was an important reminder.

Wednesday, January 28, 2009

A Simple Essay

Like many developers, I sometimes over-complicate my code, whether in an attempt to generalize and future-proof it or to just test out some new technique. In theory I know that over-complication is bad, but trying to do this in practice raises questions that I don't know how to answer. So, following a time-honored blogging tradition, I'm going to provide quotes from better known, more insightful people who address the topic, and I'll intersperse a few thoughts of my own so that I can act like I'm adding something to the discussion. (Each of the linked articles and papers is recommended reading for more treatment of this topic.)

“Controlling complexity is the essence of computer programming.” - Brian W. Kernighan (source unknown)
“Complexity is the business we are in, and complexity is what limits us.” - Frederick P. Brooks, The Mythical Man-Month, chapter 17

The main reason for pursuing simplicity is our own limitations:

“Programming is a desperate losing battle against the unconquerable complexity of code, and the treachery of requirements... A lesson I have learned the hard way is that we aren’t smart enough... The human mind can not grasp the complexity of a moderately sized program, much less the monster systems we build today. This is a bitter pill to swallow, because programming attracts and rewards the intelligent, and its culture encourages intellectual arrogance. I find it immensely helpful to work on the assumption that I am too stupid to get things right. This leads me to conservatively use what has already been shown to work, to cautiously test out new ideas before committing to them, and above all to prize simplicity.” - Jonathan Edwards, “Beautiful Code”

Edsger Dijkstra made a similar point over thirty-five years ago:

"The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague." - Edsger W. Dijkstra, "The Humble Programmer"

Part of Djikstra's remedy, as I understand it, was to push for radical simplicity in programming languages, on the belief we must be able to easily and entirely understand “our basic tools.” The trend has instead been to push more and more complexity into our tools in hopes that it will make our applications manageably simple: languages continue to sprout new features, optimizing compilers rearrange our functions and variables behind our backs, garbage collection makes resource reclamation non-deterministic, and environments like the JVM and CLI become massive projects in their own right.

Accommodating human frailty is the philosophical reason for simplicity, but there are practical benefits as well:

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan and P. J. Plauger, The Elements of Programming Style, p. 10.

(A well-known quote, but have debugging advancements like IDE integration, VM playback, and omniscient debugging and techniques like TDD changed this?)

One reason for [Extreme Programming's encouragement of simplicity] is economic. If I have to do any work that's only used for a feature that's needed tomorrow, that means I lose effort from features that need to be done for this iteration... This economic disincentive is compounded by the chance that we may not get it right. However certain we may be about how this function works, we can still get it wrong - especially since we don't have detailed requirements yet. Working on the wrong solution early is even more wasteful than working on the right solution early. And the XPerts generally believe that we are much more likely to be wrong than right (and I agree with that sentiment.) The second reason for simple design is that a complex design is more difficult to understand than a simple design. Therefore any modification of the system is made harder by added complexity. This adds a cost during the period between when the more complicated design was added and when it was needed.” - Martin Fowler, “Is Design Dead?”

Although simplicity has many benefits, complexity is often unavoidable:

“The complexity of software is an essential property, not an accidental one. Hence descriptions of a software entity that abstract away its complexity often abstract away its essence. Mathematics and the physical sciences made great strides for three centuries by constructing simplified models of complex phenomena, deriving properties from the models, and verifying those properties experimentally. This worked because the complexities ignored in the models were not the essential properties of the phenomena. It does not work when the complexities are the essence.” - Frederick P. Brooks, “No Silver Bullet: Essence and Accident in Software Engineering” (as printed in The Mythical Man-Month)

Brooks gives other reasons for complexity being an essential property of software: communication, the number of possible states, and interdependencies all scale nonlinearly, and unlike other human constructs, no two parts of a program are identical.

“I find languages that support just one programming paradigm constraining. They buy their simplicity (whether real or imagined) by putting programmers into an intellectual straitjacket or by pushing complexity from the language into the applications.” - Bjarne Stroustrup, “The Real Stroustrup Interview”

This is one of the few “pro-complexity” quotes I've been able to find. Stroustrup is arguably biased, considering the complexity of his most famous creation, but his point is valid: some complexity is unavoidable and sometimes the best you can do is to push it to the language or the libraries so that the applications can (hopefully) avoid dealing with it. This is why I think that, e.g., the Boost libraries are a good thing, despite or because of their complexity.

Complexity is a major risk to software projects.

“What is the most common mistake on C++ and OO projects? Unnecessary complexity – the plague of OO technology. Complexity, like risk, is a fact of life that can't be avoided. Some software systems have to be complex because the business processes they represent are complex. But unfortunately many intermediate developers try to “make things better” by adding generalization and flexibility that no one has asked for or will ever need. The customer wants a cup of tea, and the developers build a system that can boil the ocean [thanks to John Vlissides for this quip].” - Marshall Cline et al., C++ FAQs, Second Edition, p. 36
“What's the Software Peter Principle”? The Software Peter Principle is in operation when unwise developers “improve” and “generalize” the software until they themselves can no longer understand it, then the project slowly dies.” - Marshall Cline et al., C++ FAQs, Second Edition, p. 37

The term “Software Peter Principle” was coined in C++ FAQs, but it's at least spread enough to get its own Wikipedia page with some added discussion. “Avoid Death by Complexity,” by Alan Keefer, covers the same subject matter and is too long to quote here; just go read it.

Development methodologies can help in the pursuit of simplicity:

I suggest: Exploiting the mass market to avoid constructing what can be bought. Using rapid prototyping as part of a planned iteration in establishing software requirements. Growing software organically, adding more and more function to systems as they are run, used, and tested. Identifying and developing the great conceptual designers of the rising generation. - Frederick P. Brooks, “No Silver Bullet”

Popular perception often views “No Silver Bullet” and The Mythical Man-Month as pessimistic, but Brooks argued that the essential complexity could be steadily reduced; his approach of rapid prototyping and iterative organic growth anticipates some the agile development techniques.

“Do the simplest thing that could possibly work.” “You Aren't Gonna Need It.” - Extreme Programming maxims.

Keep in mind, though, that XP practices reinforce each other; YAGNI and “do the simplest thing” won't work unless you're also practicing unit tests (to find out if your simplest thing actually does work, and to permit refactoring) and refactoring (to keep your design clean as your “simplest” and “not needed” code necessarily grows).

As I try to figure out the balance between simplicity and complexity, I was encouraged to read that someone as experienced as Martin Fowler seems to struggle with some of the same questions:

“So we want our code to be as simple as possible. That doesn't sound like that's too hard to argue for, after all who wants to be complicated? But of course this begs the question "what is simple?" ... [One major criteria for XP is] clarity of code. XP places a high value on code that is easily read. In XP "clever code" is a term of abuse. But some people's intention revealing code is another's cleverness... In his XP 2000 paper, Josh Kerievsky points out a good example of this. He looks at possibly the most public XP code of all - JUnit. JUnit uses decorators to add optional functionality to test cases, such things as concurrency synchronization and batch set up code. By separating out this code into decorators it allows the general code to be clearer than it otherwise would be. But you have to ask yourself if the resulting code is really simple... So might we conclude that JUnit's design is simpler for experienced designers but more complicated for less experienced people?...Simplicity is still a complicated thing to find. Recently I was involved in doing something that may well be over-designed. It got refactored and some of the flexibility was removed. But as one of the developers said "it's easier to refactor over-design than it is to refactor no design." It's best to be a little simpler than you need to be, but it isn't a disaster to be a little more complex. The best advice I heard on all this came from Uncle Bob (Robert Martin). His advice was not to get too hung up about what the simplest design is. After all you can, should, and will refactor it later. In the end the willingness to refactor is much more important than knowing what the simplest thing is right away.” - Martin Fowler, “Is Design Dead?”

Of course, this struggle between simplicity and complexity is hardly new with or specific to programming.

'Tis the gift to be simple, 'tis the gift to be free...” - Joseph Brackett Jr., “Simple Gifts,” 1848