3. Design

Creative skill, past experience, a sense of what makes "good" software, and an overall commitment to quality are critical success factors for a competent design.
— Wikipedia on Software Design 

Hint

Internal design of the software is discussed here, not to be confused with user-facing functional or product design, look, feel, and artwork discussed in a later section.

With the Requirements phase complete, it’s time to design the architecture of the project. While the requirements documents described what your program must do, the design phase documents state how you plan to build it. Design is a “form of problem-solving.”  The development team brain-storms, researches, weighs alternatives, selects, and specifies what form(s) the project will take, subject to the constraints gathered in the previous phase.

_images/cell_03.jpg

Fig. 3.1 How the architect designed it.  

A completed software design phase produces documents, called specifications (or specs for short) that describe the software architecture, how the project has been organized into components, and the interfaces between them. This process is also known as Big Design Up-Front  (BDUF) and is useful to avoid needless mistakes and wrong-turns that will inevitably happen without it. Mistakes are much cheaper to fix  in the design stage than backtracking when the whole team is already charging along toward the finish line during construction. How much time should be dedicated to design up-front?  “Key Principles of Software Architecture”  on MSDN recommends:

In some cases, you may require upfront comprehensive design and testing if the cost of development or a failure in the design is very high. If your application requirements are unclear, or if there is a possibility of the design evolving over time, avoid making a large design effort prematurely.

3.1. Considerations

…the job of a software architect is to make complex things simple, not the other way around.
— Yegor Bugayenko 

Software design is an exercise in the management of complexity. The following is a list of common considerations taken into account when designing the architecture of a project.

Design Principles :

The best solutions are those created by people who realize just how small their skulls are and tailor their solutions accordingly.
— Steve McConnell, Best Practices, Keep It Simple 
  • Minimization of Complexity (discussed next)

  • Levels of Abstraction—the amount of detail considered at one time. 

  • Modularization—the division of work into components. 

  • Coupling—how tightly components are bound to each other. 

  • Encapsulation—aka information hiding, or least knowledge.
    Information given only on a need-to-know basis. 

    The single most important factor that distinguishes a well-designed module from a poorly designed one is the degree to which the module hides its internal data and other implementation details from other modules.
    — Joshua Bloch, Effective Java 
  • Separation of concerns 

  • Separation of interface and implementation 

  • Single Responsibility/Don’t repeat yourself (DRY) —Intent specified in one place only.

  • Horizontal and vertical structural partitioning 

Key Issues / Areas of Concern:

As a rule of thumb, expensive software means predictability is key while shipping. Customers need your product.
If you have a lower (or no) price tag, focus on UX. Users who don’t need your product have to want it.
— Jocelyn Goldfein, w/firstround.com 
  • Administration
  • Coding conventions
  • Communication protocols
  • Compatibility
  • Concurrency
  • Data persistence
  • Design patterns
  • Developer productivity
  • Extensibility
  • Fault tolerance
  • Industry norms (client)
  • Interaction, automated or user-driven
  • Maintainability
  • Localization, internationalization
  • Performance
  • Portability
  • Project requirements
  • Quality
  • Reliability
  • Responsiveness, timing or realtime constraints
  • Reusability
  • Scalability
  • Security
  • Testability
  • Use, corner, and edge cases
  • Usability, experience

Architecture and Technology

_images/MSDN_IC351032.png

Fig. 3.2 A Layered architectural style, courtesy MSDN (see below)

Architectural styles are common designs that shape the skeleton of an application, such as:

A number of other patterns and styles are listed  at Wikipedia.

Adoption Curve

It’s also important to take heed of where is your organization on the technology adoption  curve when making design decisions:

  • Innovators
  • Early adopters
  • Early majority
  • Late majority
  • Laggards

Slow and steady, or “bleeding-edge?” Your organization’s and/or client’s place on the curve will greatly affect the technologies and strategies chosen.

Cross-cutting concerns

Cross-cutting concerns or aspects are functionality to consider that while separate, tend to touch the whole project at different (or every) point. A few examples are logging, security, and communication.

See also

Andy Grove’s chapter on Dual Reporting (Ch. 9) from the book “High Output Management”  , discusses the same idea (multiple-planes of organization), from a management perspective.

Mistake Proofing

Good engineering eliminates users being able to do the wrong thing as much as possible. You don’t design a feature that invites misuse and then use instructions to try to prevent that misuse.
— Dan Luu 

Indeed, one of the important marks of superior engineering is the extent or effort at which the product works to prevent or avoid negative outcomes. This should/must be designed in from the get-go.

See also:  Mistake Proofing

  • How Do I Make This Software Hard to Misuse? (Part I) 
  • What If I Don’t Actually Like My Users? (Part II) 

Design Guidelines

An oft-cited work in software design is this list of guidelines from A. M. Davis:

  • Design elements should be traceable to requirements.
  • Consider alternative approaches, don’t suffer “tunnel vision.”
  • Don’t reinvent the wheel.
  • Minimize the “intellectual distance”, between the software and the real-world problem.
  • Uniformity and integration
  • Structure to accommodate change.
  • Graceful degradation and error handling.
  • Design is not coding & vice versa.
  • Quality is integral to design, not added after the fact.
  • Review design of major conceptual elements, before minutiae.

Reference

A. M. Davis, Software prototyping. In Advances in Computers, Vol. 40, pages 39-63, Academic Press, 1995.

3.1.1. Conceptual Integrity

I will contend that conceptual integrity is the most important consideration in system design… and product quality.
Having a system architect is the most important single step toward conceptual integrity.
— Frederick P. Brooks, Jr., The Mythical Man-Month (Ch. 4, 19)
_images/reims_kathedrale.jpg

Fig. 3.3 Reims Cathedral, famous for design unity over a century of construction. .

Conceptual integrity is a consistency in the overall architecture, as designed by one mind, perhaps two uno animo  (in unison). While not essential from a technical perspective, conceptual integrity is one of the marks of a quality project. One that is easier to build, understand, use, and as such less disposed to defects. The kind that feels effortless to work with; and makes users (and technical staff!) happy as a consequence. Brooks’ argues that good design is not function or elegance alone, but rather their combination.

At the opposite end of the spectrum is the “big ball of mud” , a software system with many hands “lacking a discernible architecture.” Related is the software Peter principle , described as a “dying project which has become too complex to be understood even by its own developers,” and what happens when the fight for conceptual integrity is lost—inconsistency rules.

Product Focus

Focus is about saying no. And the result of that focus is going to be some really great products, where the total is much greater than the sum of the parts.
— Steve Jobs 
Jobs: “Focus is about saying no, and it’s really a pisser…”

There will be many good ideas that come along during the design of a project of significant size. Most won’t fit cleanly. To maintain the long-term integrity of the project, an architect has to say no.

When in doubt, leave it out.
— Joshua Bloch (Unconfirmed) 

If the benefits and fit of a prospective feature are questionable or murky, postpone working on it until better information is available.

Danger:  Feature Destruction

While focus is useful, do not take it to the point of destroying functionality that people have come to rely on—start a new project instead.

For example, the would-be Jobs’es at Gnome have broken their own products  repeatedly in order to target the “grandma market.” While that’s their prerogative , there’s nothing that raises the ire, nay, fury of users more than to have features they’ve relied on to get work done disappear in the name of “simplicity.”  In comparison, the Mozilla Suite (Netscape Communicator –> Seamonkey) reimagined  as Firefox and Thunderbird was executed exceedingly well under a new banner.

The Zen of Python

One of the more coherent programming language designs is that of Python. The informational text below  (also known as PEP20, and written by Tim Peters), attempts to describe its design intent:

  • Beautiful is better than ugly.
  • Explicit is better than implicit.
  • Simple is better than complex.
  • Complex is better than complicated.
  • Flat is better than nested.
  • Sparse is better than dense.
  • Readability counts.
  • Special cases aren’t special enough to break the rules.
  • Although practicality beats purity.
  • Errors should never pass silently.
  • Unless explicitly silenced.
  • In the face of ambiguity, refuse the temptation to guess.
  • There should be one–and preferably only one–obvious way to do it.
  • Although that way may not be obvious at first unless you’re Dutch.
  • Now is better than never.
  • Although never is often better than right now.
  • If the implementation is hard to explain, it’s a bad idea.
  • If the implementation is easy to explain, it may be a good idea.
  • Namespaces are one honking great idea—let’s do more of those!

See also:  Online Resources

Further discussion on conceptual integrity , from Cunningham & Cunningham consultants.

3.1.2. Perfection

“Perfect is the Enemy of Good”

The numerous concerns listed above are all beneficial to tackle in general terms. Yet, it will normally not make business sense to invest in every area in every project. The given requirements of a project will drive a number of considerations; others will make sense as well depending on circumstances. Hunting the remainder however, will likely be a misallocation of resources with poor return on investment . Therefore, consider each best-practice with a dash of skepticism and concern for its cost. In other words, don’t let a desire for perfection  prevent you from shipping! 

As an example, additional investments in portability and reusability are valuable when code-reuse for future projects is a goal. On the other hand, when developing a web-application, work done to achieve compatibility with obsolete browsers may be be wasted depending on the intended audience. While obligatory for corporate or otherwise curmudgeonly clients, it would make much less sense for a “gamer site” to devote resources to.

See also:  “Worse is Better”

_images/bruce.jpg

Fig. 3.4 “The Boss”: New Jersey Style .

It explains why mediocrity has better survival characteristics than perfection, using Unix's triumph over the Lisp Machines as an illustration.
— Jamie Zawinski 

Something simple that’s been “shipped” and gathering feedback, often beats what hasn’t—an idea known by the phrase and post “Worse is Better” , by Richard Gabriel  . Recommended.

3.1.3. Domain-Driven Design

That shallowness of knowledge produces software that does a basic job but lacks a deep connection to the domain expert’s way of thinking.
— Eric Evans, DDD: Tackling Complexity in the Heart of Software 

From the “no-shit”  department, there’s a semi-recent movement towards Domain-Driven Design  (DDD). Unfortunately, the descriptions of which appear overly vague. To summarize: in order to properly design software for a given domain (area of study, purpose, industry, business), you’d better understand something about it!

For example, if you’re writing software conducting say… banking transactions, you better know something about banking. How about medical billing, you probably should know something about that, no? Other common examples given of subjects that require expertise, are the placing of bets on horse racing, advertising, and air traffic control. It’s not hard to think of many, many more.

Ubiquitous Language

Each subject will have its own doman-specific jargon  or “ubiquitous language” to learn as well. Once discovered, these concepts should be used as the building blocks of the design. It’s important to use domain appropriate terms to avoid an “impedance mismatch” between stakeholders and the software. From Microservices,   by Eberhard Wolff (lightly edited for brevity):

Imagine there are express orders in an e-commerce system. One possibility would be to generate a Boolean value with the name “fast” in the order table. This creates the following problem: domain experts have to translate the term “express order,” used on a daily basis, into “order with a specific value.” This renders any discussion of the model more difficult, for terms have to be constantly explained and related to each other. The better approach is to call the table within the database scheme “express order.” Then it is completely transparent how the concepts are implemented.

If you don’t have this domain expertise in house, you’ll need to work closely with a domain or subject matter expert  to git ‘er done.   If not, get someone on staff or on contract pronto.  Specific guidance on how to model and organize your classes may be found below. That’s it in a nutshell.

See also:  Online Resources

3.1.4. (π) Under-Engineering

_images/cell_17.jpg

Fig. 3.5 Response under load.  

If the resulting design cannot handle typical workloads or use cases, if it has an inefficient user interface, or lacks significant polish, we may describe it as under-engineered. It’s one of the most common design shortcomings, probably because it is the easiest to accomplish. Little education, training, or motivation is needed as prerequisites. By definition one has to achieve little more than getting the program to compile and start up.

The solution is also straightforward technically, if not politically; work harder, smarter, and/or hire better designers.

Note that as the software industry matures, customers are becoming less and less tolerant  of amateur design and unprofessional products than they used to be, even in cases where it might have been traditionally accepted, e.g. corporate B2B  (business to business) applications. These folks now have iPhones in their pockets and are no longer inclined to suffer from garbage products, though they sadly still exist and thrive under monopoly conditions.

See also:  Online Resources

3.1.5. Over-Engineering

Because successful programming depends on minimizing complexity, a skilled programmer will build in as much flexibility as needed to meet the software’s requirements but will not add flexibility—and related complexity—beyond what’s required.
— Steve McConnell, Code Complete (Ch. 10)
_images/cell_01.jpg

Fig. 3.6 Hmm… scalable seat capacity  

Conversely, in larger projects there will often be functionality incorporated which will never be used (to little or no benefit), and as such serves only to create costs in complexity and additional cognitive load . Therefore it stands to reason that complex features and flexibility should be avoided in favor of simpler alternatives when possible, especially in the initial versions, where little feedback has yet been received.

One sure way to make your crappy little business app even crappier is to build it like you're building the space shuttle.
— Jeff Atwood, Blogger/co-founder StackExchange 

That brings us to the YAGNI principle; we’ll discuss Design Patterns shortly.

YAGNI - You Ain’t Gonna Need It.

Always implement things when you actually need them, never when you just foresee that you need them.
Even if you're totally, totally, totally sure that you'll need a feature later on, don't implement it now. Usually, it'll turn out either: a) you don't need it after all, or
b) what you actually need is quite different from what you foresaw needing earlier.
— Cunningham & Cunningham (C2 wiki) 

YAGNI  is a “lazy” implementation strategy, meaning do the things you need to do, but only when you actually need to do them—avoiding unnecessary work. When following this strategy, we initially design/construct the simplest thing that could possibly function to meet requirements. After real-world use has been observed, more information will be available to help decide which features to enhance. This is the Design counterpart to premature optimization during the construction phase.

It’s hard to know the best course of action in this regard without experience. In a sizable project, my best advice would be to design for flexible “core” or platform features that can be built upon later. Higher-level features might be done as simply as is reasonable, but you’ll want the lower layers to be flexible enough to be extended, without need for major refactoring down the road.

See also:  YAGNI

Introducing the Rube Goldberg Machine

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.
— Tony Hoare 
_images/rube_gold.png

Fig. 3.7 “Professor Butts and the Self-Operating Napkin”

Rube Goldberg was a 20th-Century cartoonist who specialized in the design of amazing contraptions to accomplish elementary goals. You may have already encountered the equivalent in code; it’s not uncommon when working with “propeller-head”  types stuffing the engineering field. It takes time, thought, experience, and hard work to simplify things down to their essence; the primary reason why these contraptions persist everywhere you look.

Back to Basics

The first step is recognizing the problem. From “Software Complexity Is Killing Us,” by Justin Etheredge: 

We need to admit that not every application out there needs the same level of interface sophistication and operational scalability as Gmail. There is a whole world of apps out there that need well thought-out interfaces, complicated logic, solid architectures, smooth workflows, etc…. but don’t need microservices or AI or chatbots or NoSQL or Redux or Kafka or Containers or whatever the tool du jour (of the day) is.

When those big features are needed, it’ll be obvious because the customer will be asking for them.

3.1.5.1. Architecture Astronauts

When you go too far up abstraction-wise, you run out of oxygen.
— Joel Spolsky, Blogger/co-founder StackExchange 
_images/carlsberg.jpg

Fig. 3.8 Crack open a Carlsberg.

“Architecture astronauts” are folks adept at generalizing ideas past the point of practicality. Those good at “de-simplifying things” as a colleague of mine once complained. Rather than solving problems, they try to solve “templates of problems” instead.  This author was personally quite relieved when encountering the concept on the Joel on Software blog many years ago, and until that time had been intimidated by greatly abstracted code—thinking he must not be smart or knowledgeable enough to understand it.

Remember, if you’re not a complete moron and having trouble understanding or describing a project in concrete terms to newcomers, its a good sign that the design is too high-level.

Not everyone can waste resources at the scale that Microsoft did (see linked article), but disconnects with reality can occur just the same on smaller projects too. When encountering projects sprinkled liberally with “factories” and “XML configuration” in non-unique circumstances, do yourself a favor and “jump ship.” 

3.1.6. Bike-shedding (any color will do)

Can I get the icon in cornflower blue?
— Fight Club 

Be aware of this phenomenon, first known as Parkinson’s law of triviality.  Parkinson noted that a committee tasked to approve plans for a nuclear power plant spent a majority of their time debating trivial issues such as what color to paint the staff bikeshed, rather than the design of the plant! The more accessible (or simple) a decision is, the more people will feel qualified to comment on it—and comment they will, ad nauseam! Also related is the human need to “make a mark” or “set your fingerprint,” aka “see that right there? I did that.”

'What is it about this bike shed?'
Some of you have asked me…
— Poul Henning-Kamp 

Some years later, Danish developer Poul Henning-Kamp  introduced the story to the FreeBSD community (in the now famous 1999 email hosted at bikeshed.com ) after growing disgusted with an endless debate and discussion over whether the sleep(1) call should accept fractional seconds or not.

Tip:  Bike-shedding from the FreeBSD FAQ

Why should I care what color the bikeshed is?

The really, really short answer is that you should not. The somewhat longer answer is that just because you are capable of building a bikeshed does not mean you should stop others from building one just because you do not like the color they plan to paint it. Some people have commented that the amount of noise generated by a change is inversely proportional to the complexity of the change. 

See also:  The Narcissism of Small Differences

Sigmund Freud’s  observation that people with minor differences can be “more combative and hateful” than those with major ones.   

3.1.7. Conway’s Law

Any organization that designs a system… will inevitably produce a design whose structure is a copy of the organization's communication structure.
— Melvin Conway 

An interesting little nugget of wisdom to be aware of on larger projects is Conway’s law. If module A is developed by Department A, and module B by Department B, the interface between them will mirror the communication paths of the organization.  

Bill Corcoran’s version describes it as:

The structure of a problem reflects the structure of the organization that created it. 

See also:  Design Considerations

Additional concepts and considerations of the software design phase of the SDLC may be found below:

See also:  Books

Software Architecture and Design, Microsoft Press

This book is a practical guide to building effective, high quality applications. It is mostly platform-agnostic, despite the publisher.

3.2. Process

We try to solve the problem by rushing through the design process so that enough time is left at the end of the project to uncover the errors that were made because we rushed through the design process.
— Glenford J. Myers 

The software design process is the series of steps that transform a list of requirements and mockups into a solid plan for construction. As described below, the major activities of the design phase are:

  • Pre-design requirement analysis
  • Domain and data modeling
  • Pattern recognition
  • Selection of appropriate technologies and tools
  • Documentation

Hint

Unlike other phases of the traditional life cycle, Design doesn’t necessarily happen in a well-ordered sequence, hence there’s no step-by-step graph this chapter.

Design is typically broken down into the following two halves, however. From IEEE SWEBOK v3  (Ch. 2):

  • Architectural design (also referred to as high-level design and top-level design) describes how software is organized into components.
  • Detailed design describes the desired behavior of these components.

The output of these two processes is a set of models and artifacts that record the major decisions that have been taken, along with an explanation of the rationale for each nontrivial decision. By recording the rationale, long-term maintainability of the software product is enhanced.

As mentioned, it is useful to consider alternative designs and record the reasoning behind why some were chosen while others weren’t. A good example of these are the Python enhancement proposals , for example 308 .

3.2.1. Modeling

Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
— Linus Torvalds 

(Think databases and flowcharts my friend—this is not the swimsuit-chapter.)

_images/problem_solving_flowchart.svg

Fig. 3.9 A classic problem solving flowchart.

Early in the design phase you’ll start modeling the problem set, recognizing concepts and creating ideas and representations to describe them. Two types of modeling applicable to software are conceptual modeling and data modeling.

Conceptual,  not data but domain modeling is the mapping of a software system to help visualize it. High-level drawings such as a project manager’s Work Breakdown Structure, created with diagramming tools such as Visio/Dia, are useful here and may be reused in documentation. Flowcharts  and/or UML  diagrams may also be helpful on larger projects or difficult parts of them.

Data Modeling

A data model is a set of symbols and text used for communicating a precise representation of an information landscape.
[It] becomes a reusable map to which analysts, modelers, and developers can refer to understand how things work.
— Steve Hoberman, author "Data Modeling Made Simple," (Ch. 1-2) 

Data modeling  is the process of transforming abstract business requirements into concrete database tables and configuration. Typically, it is performed in the following steps:

Type Description Life cycle
Conceptual (CDM) One-pager on business
entities and rules
Requirements
Logical (LDM) Detailed tech-independent
business solution diagram
Reqs, Design
Physical (PDM) Detailed technical
solution (as schema)
Construction

A formal process will proceed from conceptual data models, to logical models, to the physical.  (The physical model is a bit of a misnomer however, referring to the creation of database schema,  a Greek word meaning shape, or plan.) 

Communication and Precision

A high-quality data model serves as an excellent communications device between business, development, QA, IT professionals, as well as the systems themselves. It will greatly improve the precision of requirements and specs as details get nailed down. Conversely, as the foundation of an application, a poor-quality data model can hamstring it:

Given the pivotal role played by data models, it should be no surprise that they often determine whether an application is effective. A poorly designed data model can wreak havoc on even the most elegantly designed application. Poor performance, inaccurate query results, inflexible rules, and inconsistent metadata are just some of the results of a poor data model.
— Wayne W. Eckerson, forward "Data Modeling Made Simple" 

Significance and Reality

Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.
— Rob Pike, 5 Rules of Programming 

Unfortunately, despite its importance data modeling is often done hastily in a seat-of-the-pants  style until systems come to a screeching halt, due to poor scalability, bugs or other missteps, whereby time is miraculously found to do it right. Or, you know, the business folds.

Noticing a pattern here, are ya? How about this ol’ chestnut, from “Representation is the essence of Programming,” by Frederick P. Brooks, Jr., The Mythical Man-Month (Ch.9):

Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious.

TL;DR - “Get your data structures correct first, and the rest of the program will write itself.”

See also:  Books

Data Modeling Made Simple: A Practical Guide for Business and IT Professionals, 2nd Edition, by Steve Hoberman

The subject of data modeling is big enough to warrant a shelf of books, so we’ll defer early to this beginners guide. It’s a breezy introduction to data modeling that involves the reader. If you want to know about surrogate keys, forms of normalization, and cardinality, this book will get you there. It stops short of providing performance or technical advice however.

See also:  Online Resources

3.2.2. Right Tools for the Job

Choosing the right tool for each problem is one key to being an effective programmer.
The language you use to solve a problem substantially affects your solution.
— Steve McConnell, Code Complete (Ch. 2, 16)

Which platforms, languages, algorithms, and design patterns will you use? As always the “right tools for the job,” naturally.

There are many choices to be made at all levels of the “stack,” and your job as an engineer is to make an informed decision on which to select from based on experience. Research others’ experience if yours is in short supply—avoid choosing only from the technologies you are familiar with as it will often lead to poor outcomes. For example, Python would be a poor choice to write a multi-threaded search engine, due to its slow execution speed and poor threading performance ; while still a highly productive choice for numerous other applications. C would be a poor choice for a web application (it does not even have a native string type), but would be the obvious one for a grep-like utility .

Fancy algorithms are slow when 𝘯 is small, and 𝘯 is usually small. Fancy algorithms have big constants. Until you know that 𝘯 is frequently going to be big, don't get fancy.
Fancy algorithms are buggier than simple ones, and they're much harder to implement. Use simple algorithms as well as simple data structures.
— Rob Pike on Algorithm Selection 

Additional Criteria:

  • Operating system
  • Libraries, Frameworks
  • Data interchange formats
  • Third-party platforms, services
  • Programming languages,
    typing: static, dynamic, weak, strong
    Object-oriented, functional, etc.
  • Hardware: Desktop, Mobile, Appliance, or Cloud provider
  • Build and deployment tools, containers

Experience Factor

Don’t start a new project without at least one architect with several years of solid experience in the language, classes, APIs, and platforms you’re building on. If you have a choice of platforms, use the one your team has the most skills with, even if it’s not the trendiest or nominally the most productive.
— Joel Spolsky, Blogger/co-founder StackExchange 

Indeed, choosing non-fashionable technology may not be popular, but can mean the difference between a successful project and a failure. On the other hand, the right tool for the job may not be the right one for your team if no one has enough experience to architect a new solution with it.

Anecdote Time - Poor choices

Fewer moving parts is generally preferable…
— Twirrim on HN 

Well there was “the one job” where the author and hundreds of other users were affected by a poorly performing LDAP service on-site. Its intermittent connectivity was bringing the facility to a halt as authentication was blocking everwhere. As we dug in to find out what was going on, it became clear that the service was not a standard battle-tested server in a typical configuration. Rather it had been implemented in Ruby, which talked to MySQL with DB storage mounted over NFS. Incredulous WTF’s?  abounded:

  • Ruby, a useful tool to boost development speed, exacts a large performance penalty to do so. Dynamic typing also meant runtime bugs could be lurking; there were few tests.
  • MySQL, while typically speedy is not very strict with data, potentially leading to data loss through support of lax programming. While great for blogs, it’s use here is another tell  (signal) that other parts may not be chosen well.
  • Database over NFS—holy latency, locking, and halting Batman .  While true that NFS can be “tuned,” it’s an additional layer of high-complexity that makes troubleshooting almost impossible when things go wrong. Avoid!

That’s “three strikes”  against the design if you’re counting, even before looking at code. Substantially suboptimal choices for a high-performance, mission-critical service. As the connectivity issues were costing bucket-loads of cash every day in lost productivity, it was rumored that $100k was dropped post-haste to upgrade said server hardware to ludicrous-scale . That got the issue under control (through the end of the month at least). For a never ending list of more poor choices, try the Daily WTF .

The Right People

Most organizations spend considerable effort in finding and cultivating management prospects; I know of none that spends equal effort in finding and developing the great designers upon whom the technical excellence of the products will ultimately depend.
— Frederick P. Brooks, Jr., The Mythical Man-Month, 2nd Ed. (Ch. 16)

The preceeding anecdote and quote above illustrate the necessity of competence in software architectural design, and how poor technology design choices can end up costing big money. From Steve McConnell, Rapid Development (Ch. 4):

Doesn’t everyone already do good design? No. My impression is that good design receives more lip service than any other activity in software development and that few developers really do design at all. A design architect who works for Microsoft said that in 6 years of interviewing more than 200 candidates for software-development positions, he had interviewed only 5 who could accurately describe the concepts of “modularity” and “information hiding” (Kohen 1995).

The ideas of modularity and information hiding are design fundamentals. They are part of the foundation of both structured design and object design. A developer who can’t discuss modularity and information hiding is like a basketball player who can’t dribble.

See also:  Design Competence

Additional research and several papers on competence  in software architectural design may be found at Carnegie Mellon University’s Software Engineering Institute (SEI).

3.2.3. Design Patterns

A design that doesn’t take change into account risks major redesign in the future.
— Erich Gamma, Design Patterns 

There are numerous questions and problems that have been faced over and over and over since the dawn of computing—and they’ve been solved. Millions of folks have been working on them for six decades plus. No, you’re not the first to encounter them. For many problems, there are multiple solutions with contrasting trade-offs.

Tip:  Don’t “reinvent the wheel”

Creating custom, ad-hoc, partial, immature, or buggy solutions to solve common problems yourself (as is standard at University in order to teach concepts) is a mark of the inexperienced engineer.

When there are ready-made, debugged solutions to your problem “you’d be a fool”  not to use them. Several common patterns are listed below, including two anti-patterns thrown in for fun:

Warning:  “Design Patterns Are Not Goals, They Are Tools”

Patterns, like all forms of complexity, should be avoided until they are absolutely necessary.
— Jeff Atwood, co-founder StackExchange 

On the other hand… a word of warning. You may be tempted to go “hog-wild”  with patterns as a new developer after discovering them. If one fits perfectly, use it of course, but don’t force or shoehorn them in! They are tools, not goals.     

See also:  Design Patterns

Design Patterns: Elements of Reusable Object-Oriented Software,
by Erich Gamma, et al

Also known as the “Gang of Four”  book, this is the classic yet still relevant book on the subject. Primarily focused on C++, but useful otherwise.

See also:  Online Resources

Design Patterns in Dynamic Languages, by Peter Norvig   

In this presentation, Norvig asserts the need for some common patterns are due to deficiencies in the programming languages themselves.

3.2.4. Documentation

Rem tene, verba sequentur.
(Grasp the subject, words will follow.)
— Cato the Elder, Roman Statesman 

The outputs of the design phase are specification documents, analogous to the architectural blueprints of a building. They may be organized into the following categories:

  • A functional (design) specification describes how a project will work from the perspective of the user (externally-visible behavior), and therefore useful to a wide audience.
  • Technical specifications document the internal implementation details of the project. These may be largely invisible to the user, and as such will be written specifically for engineers to facilitate construction.

Reluctance to Document

If you encounter a client who says, “We don’t have time for design documents,” candidly, you should walk away from the project because you have trouble ahead.
The spec need not be particularly lengthy; it can be just a few pages, but at the very least it should lay out the user interface, include wireframes (if there’s a UI component), and set completion milestones.
— Chris Fox, Toptal, "Why Design Documents Matter" 

Having trouble getting developers to document a design? John Cosgrove, from “Needed: A New Planning Framework”, Datamation 17-23 found:

By documenting a design, the designer exposes himself to the criticisms of everyone, and he must be able to defend everything he writes. If the organizational structure is threatening in any way, nothing is going to be documented until it is completely defensible.

Let’s keep the atmosphere positive and improve the project together, shall we?

Getting Started

The following resources should be sufficient to get you started on writing a technical specification. A short course, courtesy of trunkc at stack-overflow :

  1. Define the project goals.
  2. Define the system architecture/infrastructure.
  3. Define the database models.
  4. Define the interfaces to other systems.
  5. Define the background tasks.
  6. Define the user dialogs and the control flow.
  7. Define the non-functional requirements
    (response times, security, …)
  8. Create a dictionary for all relevant concepts/entities.

See also:  Joel’s Painless Functional Specifications

Failing to write a spec is the single biggest unnecessary risk you take in a software project. It’s as stupid as setting off to cross the Mojave desert with just the clothes on your back, hoping to “wing it.”
— Joel Spolsky, Blogger/co-founder StackExchange 

Joel’s series on functional specs is the gold standard and as always, well-written:

Tip:  Getting Specs Read

_images/vh_brown_mms.jpg

Tips on getting the finished specs read (from Joel’s articles linked above):

  1. Be funny.
  2. Writing a spec is like writing code for a brain to execute.
  3. Write as simply as possible – lots of screenshots.
  4. Review and reread several times.
  5. Templates considered harmful.

To confirm they’ve been read, consider adding a Van Halen-style rider that bans brown M&M’s backstage , a technique useful to signal compliance.

Tip:  Top Ten Typical Mistakes in Specs

Courtesy Yegor Bugayenko.
Can you find the bonus? ;-)

  1. No glossary
  2. Questions, Discussions, Suggestions, Opinions
    “Find the answers before writing, that’s what you’re paid for.”
  3. Mixing functional and quality requirements
  4. Mixing requirements and supplementary docs
  5. Unmeasurable quality requirements
  6. Giving implementation instructions
  7. Lack of actor perspective
  8. Noise
  9. “Will work, needs to work, must work…” (more noise)

3.2.5. Maintenance

Consistent with the documentation maintenance needs of the Requirement phase, project design documents are not set in stone and also will need to be maintained in parallel. There should be updates to track major changes or pivots in the project as it proceeds, realizations are made, and lessons are learned.

Wrapping up…

As you can imagine we’ve only scratched the surface of Software Design, but there should be enough here to provide a good foundation for further research, including leads to a number of resources of greater depth. In the next chapter, we’ll tackle the Construction phase.

See Also:  The Code is the Design

We must keep in mind, however, that these tools and notations are not a software design. Eventually, we have to create the real software design, and it will be in some programming language. Therefore, we should not be afraid to code our designs as we derive them.
— Jack W. Reeves 

Here’s another explanation of Software Design process taken from a different angle—that the writing of code is designing,  and code is the actual design. Do not assume that it recommends skipping design, not at all. Nor does it advise skipping design documentation, useful to visualize and communicate with stakeholders. Rather it’s a realization that detailed design is done in code. The interesting essays and commentary below explore the ramifications of the idea:

So is code the design? A current snapshot perhaps. On the contrary, my good human—the folks below theorize:

See Also:  Programming as Theory Building

Peter Naur's "Programming as Theory Building" addresses this topic of a "theory" which is built in tandem with a piece of software, in the minds of the programmers building it, without actually being a part of the software itself.
This is why during software design the first thing I talk about is not the UX flow or the software architecture, but the user's mental model (or the mental model we want to give them).
— panic & azernik on HN 
  • Programming as Theory Building, by Peter Naur   
  • The Design of Software is A Thing Apart 

See also

Architecture Of Open Source Applications, The,
Edited by Amy Brown and Greg Wilson

Architects of twenty-five open source applications explain how their software is structured. There’s a whole series now:

TL;DR 

  • Design is a “form of problem-solving.”

  • A completed software design phase produces documents, called specifications (or specs for short) that describe the software architecture, how the project has been organized into components, and the interfaces between them.

  • “The job of a software architect is to make complex things simple, not the other way around.”

  • “Perfect is the enemy of good.”

  • Conceptual integrity (consistency) is one of the marks of a quality project.

  • It takes time, thought, experience, and hard work to simplify things down to their essence.

  • “Don’t Let Architecture Astronauts Scare You.”

  • “The amount of noise generated by a change is inversely proportional to the complexity of the change.”

  • The structure of a solution typically reflects the organization that created the problem.

  • The design process is typically broken down into two steps: Architectural design (high-level) and Detailed design.

  • Consider alternative designs and record the reasoning behind why some were chosen while others weren’t.

  • “Get your data structures correct first, and the rest of the program will write itself.”

  • Design Patterns are solutions to problems that have been faced over and over since the dawn of computing.

    They are tools—not goals.

  • Use the right tools for the job.

  • A functional (design) specification describes how a project will work from the perspective of the user.

  • Technical specifications document the internal implementation details of the project.

  • Design documents are not set in stone and will also need to be maintained.