An engineer coworker (years ago now) described how he was building a feature to allow appearance themes (sets of colors) and that he admitted he was not a graphic designer (trying to push this responsibility onto other members of the team). In discussing this he also implied that this feature should be exposed to the end-user (now he’s pushing this onto the end-user?) I did not deny that he may have been doing good work technically and that separating out the visuals into all-encapsulating theme “packages” would be a fine idea, but I disagreed with the idea that just because something is implemented that the user should be allowed to or forced to deal with the details.
The simple question that bothered me was that he asked: why not?
Later I realized how to answer that question.
It is not a matter of: if the user can do it then let them do it. Users do not want that power. (Just like they do not want the power to accidentally delete their data, etc.)
The question should really be flipped around and asked: should we really bother the user with this without a good reason?
More simply: the oblivious engineer asks why not bother? (the user) but the real question is: why bother? (the user).
The point of great software is not what it allows a person to do but rather what it allows a person not to do. Not bothering the user with details that are the responsibility of the developers is a service to the user. The software should save them time, not give them extra work to do.
Why expose something that the software developers and designers can take care of on the user’s behalf? Why make extra work for them? The engineer must justify any and all features by asking these questions. If it is a matter of aesthetics only but there is no utility, or if there is negative utility (more work for the user to do that they shouldn’t be bothered with), then that should tell us something.
In contrast, allowing the user to change the colors of individual items of data for semantic reasons is obviously a good idea when it is a core part of the visualization aspect of the specific piece of software and allows users to make their intended meaning instantly identifiable, which saves them time and enhances the power of the software’s visualization.
But allowing the user to theme parts of the interface that really have no bearing on the core point of why people would use the application just creates extra features to maintain and extra cognitive load for the user. Especially when those parts of the interface use standard controls on other platforms and allowing the user to change it on one platform would create inconsistency.
So in writing this essay, I figured out what it was about his comment that bothered me so much. And the problem wasn’t specific to him necessarily but rather the attitude of a lot of programmers and their obliviousness to the importance of design from the human standpoint, not from the artsy-fartsy standpoint. Even worse is when they say, “I am not a designer nor do I claim to be one,” but they think that design only has to do with choosing colors and making things look pretty! Or their understanding of design is so lacking that the features they implement have to be rebuilt because of their lack of understanding of how to build and test user-facing features, their lack of understanding of what to build in the first place, or whether the feature should or should not be built in the first place!
You see this all the time with lots of “powerful” open source software: instead of doing the work up front to prevent the user from doing stupid things, the engineer punts and says, “Let the user deal with this: here are myriad sliders and controls and they can just make sure the combination of settings makes sense. (Coincidentally, each slider is trivial to implement!) I am doing the user a favor by directly exposing all of the internal knobs and levers for them to tinker with. If they get the software into a bizarre state that is on them.”
An example of this in a single domain: Readium and Calibre (two very useful and powerful OSS projects) vs. Apple’s iBooks epub reader app (now Apple Books, iOS and macOS). Apple’s app allows you to change the appearance of an ebook that you are reading, but notice that there are only four pre-made themes or foreground and background color combinations, and notice that no matter how you rotate your device or change the text size, you cannot get it into a bizarre state. As a bonus, single v. double column mode is entirely automatic. Contrast Readium and Calibre, where there are more controls but it is too easy to combine visual settings in a bizarre manner, for example text columns that are too thin or too wide, having to manually turn single v. double column mode on and off manually, red text on a blue background, etc. No thought for preventing undesirable states, and pushing the responsibility onto the user.
A good engineer who thinks in terms of “design is how it works” will move mountains (write lots of thoughtful, technically challenging, invisible code) to make the software more clever in order to save the user just a small fraction of extra work, and to avoid having to add a manual setting for the user to fiddle with. Just because the engineer knows that internally the machine works by taking a bunch of knobs as input and producing some output doesn't mean they will then directly expose all those knobs as the interface for the user. Some thoughtfulness is required.
Software design is a subtle art. Some engineers take for granted their relationship with the end-user of the software. Engineers excel at using diverse tools to create custom software to make a computer do whatever they want. Engineers often favor a toolkit approach, where maximum flexibility is paramount. Ease of use is less important because the engineer is an expert and understands in excruciating detail how the machine works and how her software works or does not work. Fragile workflows or complicated multi-step workflows are considered acceptable if new objectives can be achieved with less total work for the programmer/engineer. Make the machine do what I want. The end justifies the means.
Users on the other hand favor ease of use, abstraction and integration. Users are uninterested in the technical details of how something works and are task-focused. They care about how a task is done (the means), how to remember how to do it, how to discover how to do it, how to avoid extra work. They want their software to just work and they want the interface to reflect the exact state of the data at all times. They value integration, where a single piece of software can do everything they can imagine with their data without having to make the user manage interoperability between multiple applications. They are uninterested in what is possible to do by combining different tools in clever ways and just want an easy, reliable way to go from idea to implementation with minimal fuss. They don’t want to be responsible for understanding the underlying details to accomplish their work or even worse, safeguarding the integrity of their data. They don’t want problems to be their fault. They don’t want problems at all. Their powerful machine and powerful software should see to that.
What if you could learn music theory from a mentor that had all the answers? Start with what you know and follow your curiosity, confident that you have all musical options at your disposal, since Harmonious is the most complete chord and scale map. Find extensions, substitutions and chromatic flavors that lie at the edge of the familiar and the exotic. See connections through the dual lens of chords and scales, linking harmony and melody.
Several years of work and a few decades of studying music theory and the result is something I am proud to announce. Harmonious is live on the iOS App Store and live on the web at https://harmoniousapp.net/!
If you have an iOS device (iPad and iPhone, even iPod touch) contact me and I can give you a promotion code so you can download the app for free if you do not feel like dropping $4.99 on the app. If you are so inclined, you can leave a star rating and maybe write a review.
If you are interested in hearing more about Harmonious when there are updates and new features in the future, please sign up for the infrequent Harmonious Email Newsletter. You can unsubscribe at any time.
If you do not have an Apple/iOS device, but you are at a computer or you have an Android device, please feel free to check out the app live on the website at https://harmoniousapp.net/. It should look good on Android devices and large screens too, and if it doesn't I would love to hear your feedback.
Here's to a great start on an ambitious project!
A casino on the Las Vegas Strip, the Bernoulli Hotel and Casino, has introduced a new dice game, called Red, Purple, Silver. The game uses (up to two pairs of) three six-sided dice:
These are fair dice, regulated by the Nevada Gaming Commission, meaning no one is cheating anyone by making dice that favor landing on any side over the others. You can even test this yourself before you play the game.
There are two modes of play, called Game 1 and Game 2.
In Game 1, you play twenty rounds of the following: player rolls her single die once, dealer rolls his single die once, and the higher number wins the round. The winner of the game is the person who wins more rounds. If both win ten, extra rounds are played until a winner is determined. The bet is applied to the entire game, not individual rounds and cannot be changed until the end of the game.
In Game 2, you play twenty rounds of the following: player is given two dice of the same color to roll, rolls them both simultaneously, and sums them. The dealer does the same with his two same-colored dice. The higher sum wins the round. Ties (a round with the same sum for player and dealer) are re-rolled and do not count as one of the twenty rounds. The winner of the game is the person who wins more rounds. If both win ten, extra rounds are played until a winner is determined. The bet is applied to the entire game, not individual rounds and cannot be changed until the end of the game.
Before die selection (see below), the player bets a certain minimum by moving their stack of chips to a certain place on the table, Square A or Square B. When the twenty rounds are over, if the dealer wins either Game 1 or Game 2, he keeps your stack of chips. If you win Game 2, you get your stack back plus a new stack the same size. Double or nothing. If you win Game 1, you get your stack back plus a new stack the same size plus a smaller stack one third the size, rounded down. 2.3333x or nothing.
When you walk up to the table, you do not get to choose Game 1 or Game 2 directly. Instead you choose from Scenario A or Scenario B by placing your chips in Square A or Square B, respectively. After bets are paid off, dice are returned to the dealer, and the player can again choose Scenario A or Scenario B and play another twenty rounds.
In scenario A, the player chooses her die color first and chooses Game 1 or Game 2. Then the dealer selects a die color and they play twenty rounds of the game chosen by the player.
In scenario B, the dealer chooses a die color first, then the player chooses her die color, then the dealer chooses Game 1 or Game 2, and they play twenty rounds of the dealer's chosen game.
Will the casino make money over the long term? Should you play this game? (This is a trick question. Casinos only play games where the dealer makes money over the long run. If the casino thinks the game is good for the casino, you should never play it! Unless it is a game of some skill, like poker. But even then, other players will probably kick your butt.)
For the answer and some analysis, see my earlier article about the dice.
A friend asks:
[Functonal Programming in Python] is an interesting way to see things. I find it fascinating, but what do you think is the main benefit? For example, lambdas are cool syntactically, but I don't see a huge reason for them other than sugar. Python seems to treat function objects basically like function pointers in C, and lambdas are like safe, single expression C macros that you loop through lists. Does functional programming offer more than syntax? Even if it's merely syntax to help one think in a different paradigm that's valid, but I'm struggling to see the value here in the beginning, whereas with C++ the benefits of OOP was easily apparent.
In addition to passing first class functions and having APIs with callbacks, etc., one of the main benefits to functional programming revolves around state. All old state is never updated or copied or klobbered in-place, but is kept around as is. All data structures in purely functional languages are 100% immutable. This eliminates large classes of bugs (but makes coding certain kinds of complex state updates a little more awkward, honestly) and makes testing easier and more mathematical. This also allows 'time-travel' or 'undo' or rewind at the granular level.
These special vector collections and hash maps (dictionary / object), based on work by the late Phil Bagwell, allow basically O(1) lookups and appends with no copying. (There is more overhead/book-keeping than normal low-level C-style data structures but the Big-O performance is still shockingly amazing and scalable.)
This fits with the expression-based approach that most FP languages encourage because you can usually write code that describes declaratively how to compute something, instead of the steps and order to compute it. And those functions can be tested independently since there is little or no 'implicit runtime state' to have to supply at testing time.
Imagine looking at a code base and seeing each function in isolation and knowing (Haskell, at least!) that each piece of code completely and explicitly tells you what inputs it takes, and what output it computes, and that each function has zero side effects (and hence no other effect on any other code ever, just taking CPU time and allocating some memory and returning a result). This also means that if you can prove (formally or informally) that a set of functions are correct for all of their individual inputs, and a combining function is correct, then the combination of those functions is guaranteed to be correct. In normal programming, this is not the case because of implicit, hidden global (or localized) runtime state. When refactoring a chunk of imperative (non-FP) code, what you often end up doing is making the inputs explicit, etc. so that the block of code can be cut out and isolated and used anywhere else. In FP this is already done, every where!
FP is not a panacea but purely functional (stateless) coding style (in C#/C++ = static classes with only static methods and no static mutable state) is awesome because it makes all dependencies explicit instead of hiding state in a 'this' object, which is just a slightly more abstract version of the global state anti-pattern—now your global state is just hidden behind myriad objects! Purely functional code can be used anywhere 'as-is' and expected to work in all circumstances, with no semantic (incorrectness) penalties for reordering calls to functions, etc. You can write large amounts of code in OOP languages in a purely FP coding style and get 90% of the benefit of FP, (but you will still wonder about that last 10%). For example, objects with no mutable state, just set their fields once in the constructor, and provide methods that compute things but do not mutate internal state, etc.