Wikipedia defines User-Centered Design (UCD) as “a process (not restricted to interfaces or technologies) in which the needs, wants, and limitations of end users of a product, service or process are given extensive attention at each stage of the design process. User-centered design can be characterized as a multi-stage problem solving process that not only requires designers to analyze and foresee how users are likely to use a product, but also to test the validity of their assumptions with regard to user behavior in real world tests with actual users. Such testing is necessary as it is often very difficult for the designers of a product to understand intuitively … their design experiences, and what each user’s learning curve may look like.
“The chief difference from other product design philosophies is that user-centered design tries to optimize the product around how users can, want, or need to use the product, rather than forcing the users to change their behavior to accommodate the product”
Paul Graham essay: A taste for Makers
Motives for this post:
(1) UCD is heavily emphasized in front-end development. But programmers are people too, and so UCD should therefor be talked about in other aspects of programming. I’m not saying its not. But I would like to synthesize how it is. I also think it can be a lens for large leaps in improvement of the programing design process.
(2) Start-ups are inherently user-centered since their product/service must have traction. This differentiates them from large competition (like Google, who can use its resources to rapidly accelerate the development of a project) which is driven by profit.
(3) There are some misconceptions in programming design that results from thinking that it is constrained by the boundaries of physics and the real world.
structure of this post:
the layers of design: visual design – information design – structure – requirements
iterative cycle: Concept -> Design -> Implementation -> Concept -> Design -> … ->
The structure of this post is to first talk about the layers of design, and then look at aspects of programmings iterative cycles. Note, this is only an attempt to follow interesting intersections between programming and my understanding of UCD, and is far from exhaustive.
all quotes are from The Pragmatic Programmer by Andrew Hunt and David Thomas
The default definition of user as I use it here is another programmer.
* * * The Layers of Design * * *
visual design: recall vs recognition: GUIs vs efficiency
the aesthetics of programming are (rightly) in line with Minimum Viable Products (MVP) because of its culture around efficiency.
GUIs provide buttons and text lists for you to recognize the possible event options. Its development is viewed by some as an overhead, as they limit the capabilities of the familiar user (aka not the end-user) who would sooner use the underlying methods to achieve their goals programmatically.
Alternatively, using a terminal (recalling the command and its configurations) requires more initial investment, with the benefit of being able to be more efficient in the long run.
Those who develop for end-users (GUI designers) seem to really enjoy holding the label of “empathetic programmer”, and I really value people who can challenge the status quo in this space (such as Bret Victor). However, those farther back in the technical stack have the freedom to value efficiency above all else. The latter is where I’m personally more interested.
Information Design: Programmers as detectives, and an evidence-blackboards as a platform for agile development
“Features of a detectives blackboard are:
- None of the detectives needs to know of the existence of any other detective–they watch the board for new information, and add their findings
- the detectives may be trained in different disciplines, may have different levels of education and expertise, and may not even work in the same precinct. They share a desire to solve the case, but that is all
- Different detectives may come and go during the course of the process, and may work different shifts
- There are no restrictions on what may be placed on the blackboard. It may be pictures, sentences, physical evidence, and so on.”
“Since we can store objects, we can use a blackboard to design algorithms based on a flow of objects, not just data. It’s as if our detectives could pin people to the blackboard–witnesses themselves, not just their statements. Anyone can ask a witness questions in the pursuit of the case, post the transcript, and move that witness to another area on the blackboard, where he might respond differently (if you allow the witness to read the blackboard too).”
“Blackboards can help coordinate disparate facts and agents, while maintaining independence and even isolation among participants”
here the user is the product manager / solutions architect.
agile development tool: sprint.ly
Structure: Interface Accessible Data Structures
When making a a database accessible through, say, a REST API, it is crucial to optimize the data modeling in response to its use cases
Query-driven data modeling on Cassandra: Cassandra is a Column-oriented NoSQL database, which simply means that each row is equivalent to an SQL table. Queries are optimized to retrieve these “row-tables.” Therefor, it demands that the data structure be created in anticipation of the query.
Requirements: Design by Contract
“Dealing with computer systems is hard. Dealing with people is even harder. But as a species, we’ve had longer to figure out issues of human interactions. Some of the solutions we’ve come up with during the last few millennia can be applied to writing software as well. One of the best solutions for ensuring plain dealing is with the contract.
A contract defines your rights and responsibilities, as well as those of the other party. In addition, there is an agreement concerning repercussions if either party fails to abide by the contract.”
Design By Contract (DBC, Bertrand Meyer) is based on three expectations:
- preconditions: what must be true in order for the routine to be called; the routine’s requirements. A routine should never get called when its preconditions would be violated. It is the caller’s responsibility to pass good data.
- Postconditions: What the routine is guaranteed to do; the state of the world when the routine is done. The fact that the routine has a postcondition implies that it will conclude: infinite loops aren’t allowed
- class invariants: a class ensures that this condition is alwasy true from the perspective of a caller. During internal processing of a routine, the invariant may not hold, but by the time the routine exits and control returns to the caller, the invariant must be true. (aka methods of the class should preserve the invariant. The class invariant constrains the state stored in the object) An example is a loop invariant, which defines the loops goal, and is also valid on each iteration through the loop. In a sense, it is a micro-contract.
These concepts should be approached with “lazy” code: be strict in what you will accept before you begin, and promise as little as possible in return.
An example would be inheritance and polymorphism where the subtype must truly be “a-kind-of” the base type: “Subclasses must be usable through the base class interface without the need for the user to know the difference” (Liskov Substitution Principle)
The benefits of DBC is that it forces the issue of requirements and guarantees to the forefront. Most problems are detected at the boundary between your code and the libraries it uses.
“Simply enumerating at design time what the input domain range is, what the boundary conditions are, and what the routine promises to deliver–or, more importantly, what it doesn’t promise to deliver– is a huge leap forward in writing better software”
Pitfalls could result from confusing “requirements that are fixed, inviolate laws with those that are merely policies that might change with new management regimes.”
Treat contracts dynamically; as a place for dialogue of supply & demand resources. Morality aside, it is important for programmers to act autonomously from business directives insofar as they can better evaluate the requirement’s technical necessities.
* * * The Iterative Cycle * * *
Concept: API Development
Test Driven Development (TDD) is the ideal of writing tests before you write code, then writing the minimum amount of code to pass the test, and then iterating (refactoring) to acceptable standards as the code grows. Tests involve (ideally) all the edge-case patterns of usage (“evil use cases”) + unit testing, and can therefor be a “mock-up implementation” of a contract between your exposed code (API), and user requirements.
You should assume diverse user capabilities. This will promote pattern flexibility (greater configuration), larger applicability (minimize code re-use), and lets you handle edge case testing.
User goals for APIs are addressed with documentation. Three general goals are: (1) tell me (2) show me (3) involve me. A “Tell me” user goal relates to efficiently viewing functionality of the exposed methods. A difficulty resulting from the efficiency trade-off is not being able to see the forest for the trees (i.e. not communicating implied patterns for usage). This difficulty creates “show me” users (think stack overflow). This requires documentation separate from implementation, which potentially creates backwards compatibility issues (annotations prevent this to some small degree by coupling code to metadata). The “involve me” user benefits from architectural explanations and code that documents itself.
Design: Cheap Experiments with Prototyping
Prototyping can be tried out on risky or uncertain elements without committing to building the real item. Post-it notes are great for prototyping dynamic things such as workflow and application logic. This process is useful for anything that carries risk, hasn’t been tried before (unproven, experimental, doubtful), or is absolutely critical to the final system. This could include architecture, new functionality, structure or contents of external data, third-party tools, performance issues, and user interface design. At its core, prototyping is a learning experience. Its value is not in what is created, but in the lessons learned.
Implementation: Tracer bullets as Minimal Viable Products (MVPs)
“Tracer bullets are loaded at intervals on the ammo belt alongside regular ammunition. When they’re fired, their phosphorus ignites and leaves a pyrotechnic trail from the gun to whatever they hit. If the tracers are hitting the target, then so are the regular bullets.
“Not surprisingly, tracer bullets are preferred to the labor of calculation. The feedback is immediate, and because they operate in the same environment as the real ammunition, external effects are minimized”
The benefits of tracer bullets are that (1) users get to see something working early (2) Developers build a structure to work in (3) you have an integration platform (4) you have something to demo (5) you have a better feel for progress (6) less sunk costs if your tracer bullet doesn’t hit its target.
This commitment benefits from quantifiable confidence that you are choosing the right tool for your specific problem. Sometimes, inertia can make this tool selection difficult. If someone argues that a tool is “the best”, then ask for a comparison with the other options. They are probably acting on personal inclinations, and not in response to the problem at hand
Design vs Implementation: Prototypes vs Tracer bullets
Prototyped code is thrown away after the lessons are learned. It creates explicit pieces of context (ex: a mock-up) as would a reconnaissance and intelligence mission would. It prefers convention over configuration frameworks and recognizes (as everyone should) that premature optimization is the root of all evil. Alternatively, tracer coding is lean, but complete, and forms a skeleton for the final system.
A single tracer bullet is fired after completing sufficient exploration with prototypes.
Iteration: Augment + Refactor
Refactoring is the process of rewriting, reworking, and re-architecting code. At its heart, it is redesign. Refactoring is useful to (1) mitigate duplication (2) abstract away nonorthogonal design (3) boost performance and (3) respond to outdated knowledge. When not handled, it adds to the code’s technical debt:
“Think of the code that needs refactoring as a ‘growth.’ Removing it requires invasive surgery. You can go in now, and take it out while it is still small. Or, you could wait while it grows and spreads–but removing it then will be both more expensive and more dangerous Wait even longer, and you may lose the patient entirely.”
Expert intuition of the domain is incredibly valuable in accelerating the iterative process by providing a heuristic for efficient initial frame-working (which is characteristic to an erudite architect). However, lots of software development is emergent (i.e. unpredictable or idiosyncratic to the problem at hand):
“Unfortunately, the most common metaphor in software development is building construction… Software doesn’t quite work that way. Rather than construction, software is more like gardening–it is more organic than concrete. You plant many things in a garden according to an initial plan and conditions. Some thrive, others are destined to end up as compost. You may move plantings relative to each other to take advantage of the interplay of light and shadow, wind and rain. Overgrown plants get split of pruned, and colors that class may get moved to more aesthetically pleasing locations. You pull weeds, and you fertilize plantings that are in need of some extra help. You constantly monitor the health of the garden, and make adjustments (to the soil, the plants, the layout) as needed.
“Business people are comfortable with the metaphor of building construction: it is more scientific than gardening. it’s repeatable, there’s a rigid reporting hierarchy for management, and so on. But we’re not building skyscrapers–we aren’t as constrained by the boundaries of physics and the real world.” (The Pragmatic Programmer by Andrew Hunt and David Thomas)