Aggregate \Ag”gre*gate\ n. – a mass, assemblage, or sum of particulars; as, a house is an aggregate of stone, brick, timber, etc.
When we first learn a new thing, it’s the particulars that stand out to us. A pre-toddler learning to walk must focus on each and every step. But as we grow, the particulars fade into higher-level ideas and skills. As adults, we rarely think about individual steps; instead, we simply go for a walk.
The same pattern is found in professional fields, such as science. The particulars of orbits and falling things gave way to the aggregate idea of gravity. When scientists first discovered electrons, they imagined them as single, planet-like points orbiting an atomic nucleus. Since then, scientists have given up the idea of point-like electrons, replacing it with the aggregate concept of an electron probability cloud.
The science and art of programming trends the same direction. As a profession, it began with the flipping of literal switches, transformed into punch cards and hexadecimal and machine language, and eventually reached the high-level languages we commonly use today.
But our work is never done. Aggregates have a tendency, given time, to become new particulars. Today’s high-level languages often still manually manage memory or express algorithms step-by-step. We write individual unit tests and receive single, momentary errors.
Hopefully, though, these particulars can be replaced by new aggregate ideas and tools. Here are just a few of the possibilities.
Declarative programming focuses on what you want rather than how to do it. Instead of thinking in step-by-step instructions—moving values to variables, iterating each item in a collection—declarative programming urges you to express the problem more directly.
Domain-specific languages often try to do this. LISPs like to transform whole collections at a time, and vector-based programming languages apply operations on collections to the individual values within them, no matter how deep, eliminating the need for loops. Logic programming is another way to declare what you want rather than how to get it.
Fuzz testing and property-based testing are tools for moving beyond individual, hand-written unit tests. They generate whole test suites for a single function based on the programmer’s expectations about the function’s behavior.
Mutation testing also generates tests, but rather than testing an application’s code, it tests the test suite. Instead of depending on a programmer’s line-by-line examination of a program to figure out which edge cases aren’t tested, mutation testing modifies the source code and checks whether any tests fail; when they don’t, either the code is unnecessary, or, more likely, it isn’t being tested.
Elm introduced a time-traveling debugger based on the ideas of Bret Victor, and other languages and frameworks have begun to pick up the idea. Rather than stopping program execution at a single breakpoint, a time-traveling debugger can play different code through the same series of events, providing an apples-to-apples comparison of the software’s behavior.
Program errors could use the same treatment. We take stack traces for granted these days, but with sophisticated, stateful applications, it’s important not only to know where the error occurred, but also what preceded it. In addition to a stack trace, we also need a history trace so we can reproduce bugs quickly and fix them faster.
In a world where jQuery dominated and updating individual DOM nodes was the norm, React.js introduced the practice of thinking in whole DOM trees, diffing the current tree against the new tree and applying the necessary changes without help from the programmer. Developers can now think in terms of how the UI represents the state of the application rather than how to manually apply changes.
GraphQL and JSONGraph are two ideas currently surfacing, hoping to replace traditional REST and other resource-oriented APIs. Rather than fetching an individual resource, a graph API returns the graph of resources the client requested. Perhaps soon, we’ll no longer have to stick to a rigid side-loading pattern or create a new API endpoint for yet another resource.
Style Regression Testing
Style regression testing is on the horizon. Services like Percy.io promise to eliminate the need for a human to hunt for styling problems page-by-page and state-by-state throughout the application. CSS can be tested version-to-version the same way other code is tested with unit and integration tests. Even better, writing style tests are automated; there’s no need for developers to specify what styles should be outside the CSS.
The idea of working in aggregates instead of particulars applies to more than just programming. It can be applied to business too, letting people move beyond “my way or your way” to consider the spectrum of tradeoffs between the two. Or, rather than thinking in terms of problems and solutions, it lets us think in terms of whole cause and effect trees. Whether in programming or in life, this big-picture approach can be a valuable approach to solving your next challenge.