I've written some previously about the OBJ hacking programming project I've got going on. Mostly I've written about issues encountered with various tools, compilers etc, but not much about architecture and design.
The guts of the project - its core architecture and original code base - date back to around 1984. The amazing thing as its been expanded to include 32 bit records and a many new OBJ record types is that the core has remained stable. New capabilities, features, and workaround for certain bugs in vendor generated OBJ's haven't required any significant design changes at all. Where all the new stuff would plug in was readily apparent and straight forward. The code base today has probably grown by about 3X since the 1984 vintage original, yet new pieces are still dropping right in without any special hoopla. Much of the new code falls into the category of a "code museum" where all the dirty laundry various compiler vendors ever hid gets exposed and dealt with. Its sort of a "living history" of compiler back ends, and their code generation quirks, over the past 22 years. Yet the code is still (relatively) easy to understand and maintain, even though it deals with some of the most horrible hacks ever perpetrated over the last quarter century.
Why? Good design and architecture. Reasonable data structures, and distinct phases of processing were chosen back in 1984. The thing converts OBJ's into a linked list of internal records. Those internal records turned out to be flexible enough to represent both 16 and 32 bit flavors of various things. I can shuffle them like a deck of cards, move'em around, insert new ones, delete others and change anything I like with ease and the OBJ generator phase just takes the list and spits out a new OBJ module. The linked list was the right way to represent this particular class of data.
I C++ programmer would look at this architecture and code and declare it to be essentially "object oriented" even though its plain ANSI C. All the syntactic trinkets of C++ are missing, but the ideas are there. Heck, in this regard I've been writing OOP assembler code for 25 years too. Object oriented isn't about tools, its about how you approach a task.
The tools, as in the case of C++, are actually quite horrific in their design, promote arcane problems, and foster confusion. There are some conveniences of course, but the whole gestalt of C++ itself was forged in the darkest of Torquemada's torture chambers. I will be avoiding it for this OBJ hacking project.
How does one tell a good design from a bad? Its not always apparent. If you can add new features a year later without whining about how crappy the code is, you probably have design that isn't horrible. If the code base can survive for 5 years or so, and still be readily maintainable, it was probably a good design to begin with. If the code can transition to different platforms without major rewrites all over the place, its probably not too bad.
I know I could wrap a fairly functional Windows or Motif front end around this OBJ hacking tool in a matter of days because of the way its designed. The code bases for many command line apps had a real hard time making that kind of platform transition. Lotus and Ashton Tate struggled with their code bases years ago. Lotus struggled for years getting the "new" Lotus v3.0 product out the door, and ultimately wound up producing another rev of the Lotus 2.x product when the 3.0 stuff faltered in the market place because of performance issues.
I suppose good designs are as Rehnquist said, kinda like porn - hard to quantify, but you know them when you see them. The real test is in their durability over the years. If the maintenance programmers are always whining about crap and wanting to rewrite stuff, you probably don't have a good design. In fact you may not even have an actual design, rather just a collection of code blobs stitched together with bubble gum and bailing wire.