Habits of Expert Software Designers
thereader.mitpress.mit.edu
thereader.mitpress.mit.edu
But the problem is, unless you already have the requisite experience to show you what these bullet points really refer to in practice, you're not going to really make sense of this list, not in a way that will actually help you do anything differently.
"Always learn about the users" for example is much too vague: which users? how do you talk to them? when do you trust them and when do you not take their feedback at face value? I don't think you can learn this sort of thing outside of a real life context.
> It is only by harsh experience that we learn which principles take priority over which other principles; as mere words they all sound equally persuasive.
This is why, when I find myself in a situation like that, I try hard to understand the point. Why do these people do these strange things? What do they mean by saying these sentences that seem detached from the practice? Empirically, they are smart, so they mean something.
This is how you start to detect where the common context is lacking, and look for explanations. Eventually, with enough effort, you bridge the gap and understand what they meant, and why. This is how you grow.
In effect this is what I'm doing when I coach interns and juniors -- giving them the requisite knowledge so that they can see things the way I'm trying to explain them.
A correct answer: you need to learn how to read, then some mathematics, then quantum physics, then you can calculate how sunlight scatters on molecules of air. Understanding the physiology of color vision won't hurt, too.
This is not very useful. But at least you see some topics to learn more about, and a simplified picture may be built: sunlight contains some blue light, and it gets stuck in the air, while reds and greens pass in more easily. Hey, you just understood why sunsets are orange and red! Go on.
Light is a bit like a wave through the air and in the blue light the waves are closer together.
The sky aboves us has a small amount of dust.
Because the blue light waves are closer together they hit more dust.
This means they are more likely to bounce towards us, so we see blue when we look up at the sky.
At sunset the blue waves are more still more likely to hit dust, and so be blocked, so we see red.
Not simple enough, but I gave it a go.
"Let's start with you reading (and demonstrating to me a core competence of) the basic texts of physics leading up to Rayleigh scattering--in this case, that would be Newton, Liebniz, Maxwell, Einstein, Heisenberg, and Schrödinger. When you have those six down, I'd be happy to explain to you why the sky is blue."
Inspired by: https://news.ycombinator.com/item?id=1492061
The 5-10 minutes explanation is what you get in a lecture. You may even think that you understand afterwards. Years later, you're going to look back and realize that you really didn't understand much at all.
If a topic is muscle memory, it becomes very hard to explain or teach to another. This is why it is important to learn topics beyond muscle memory. I had to go through this because I learned how to program before I was a teen. I found it difficult to explain what I was doing to others.
Today when people get stuck early on learning to program I show them decomposition, as it is the most common hold up people get stuck on. Decomposition is the opposite of abstraction, so it leads into a beneficial second lesson later on. (I don't explain decomposition, I show them.)
At the end of the day it comes down to personality and beliefs more than prerequisite experience when it comes to learning. If someone is afraid to stop and go out of their way to learn a prerequisite, because they're afraid to be seen as ignorant, they're going to struggle. Likewise, if someone is afraid to make a mistake / have a misunderstanding, it can cause too much anxiety to quickly pick up topics.
This is why with interns my primary focus is neutralizing anxiety. I try to show ignorance and misunderstanding are okay and acceptable. I lead by example. Little ducks copy unsaid behaviors well. Once that criteria is met, then and only then do I slowly switch into dumping terminology and lessons on them, only once they're ready for it.
The first bullet point — listening to users, but not taking what they say at face value is fantastic advice for all businesses. Executing on it is a high-skill activity.
I can give examples of when this was neglected and things went poorly. I can give examples of it done very correctly. Only after practicing it for years can I get a smell for what a user really needs, or how to steer a corporate customer to the solution they need and not the one they want.
Now you can hand wave this and get a basic idea of it. But this is also undergraduate mathematics. The abstractions keep building on each other as you go and soon the easy way to explain it is still in terms of other abstractions. This is for instance why research papers in mathematics are impenetrable, because the requisite knowledge takes years to acquire.
Haven’t you ever re-read a book after gaining much practice and realized how much you’d missed on the first time reading? I do that all the time.
-- David L. Goodstein, Feynman's Lost Lecture: The Motion of Planets Around the Sun, ISBN:978-0393039184.
I wonder if other physicists shared the sentiment.
To know what what you do not know, etc. (c.f. confucious 2:17, rumsfeld 2:02)
I always seek clarity, and quite often 'complex' science is just about that, you get to see differently, eurekas. And when there's no eureka, to me then something is wrong, and the science has been accepted at face value which is also wrong.
If this were to be levied at me accusingly I'd throw a programming Bible at them, get thee hence and study.
The worst engineers dismissed others out of hand, saying they lacked the requisite knowledge for a discussion.
This is so patently untrue. Folks like Feynman are the exception, not the rule.
With some imagination I think that anyone working with software can take something from these points, but it's obviously not a how-to-expert tutorial. Rather, it's what the title says that it is.
My thoughts as well. Or, you will think of course, I do these things, when in fact you are ineffective at them.
Based on the excerpts you can see on Amazon, not really.
I think the premise is that if you did not looked at users before and now you know that you should be, it's a huge difference and you will start thinking about questions like you asked and will figure out the answers over time.
I think sometimes the hard part is not knowing what you don't know, because those might be very hard to discover.
While there may be a TON of things alluded to when calling something elegant, generally people mean that it's easy to understand, thoroughly solves the problem, and isn't likely to break. I would imagine that many elegant solutions become legacy code eventually because they don't easily die and don't necessitate placement all on their own.
I'd be interested to hear how other people view/use this word. I haven't given it much thought, but now that I am, it seems to be very important.
I think your description of elegant is good. One more aspect of elegant is: doing all those things you list here in a manner idiomatic to the language and paradigm you're working in. So for example, regardless of how efficient or easily understood your solution is, if you're using loops and mutation in a functional language, it would rarely be considered "elegant" by experienced people working in that paradigm (that's not to say that loops and mutation aren't very occasionally a good idea in functional languages, but they'd still not be considered elegant).
In Feynman's words, "If you can’t explain something in simple terms, you don’t understand it"
Just ask them why they pick that certain color and you won't find rational behind it. They just...did, their hand just move to that color, paint it, and it looks good—the results of tons of practices over the years. Not to mention some artist didn't even have proper art education, not to mention art theory. Why and how it happened to be the same combination as in the color harmony theory is surely hard to explain.
The trouble is you can't teach the intuition.
Error might be opportunity but it's best to triage and fix the immediate impacts of the error, document the root causes, then explore any potential opportunities later.
Similarly, you should put out a burning computer with an extinguisher. It might be neat to know that computers can start fires but I recommend matches or lighters if you want to roast marshmallows.
So, I agree, and I think these articles are actually harmful. The biggest skill is knowing what the most important thing to do is, how much of it to do, and why you're doing it. Doing something because you read it's a best practice done by experts is almost the direct opposite of that because it does nothing to help you prioritize, scale or understand.
Don't be marshmallow guy.
So how should "these bullet points" explain those things "outside of real life context"?
These points give you abstract points to consider and to then apply to your problem.
Besides having the requisite experience to make decisions, you also need to be in a position of authority that can make decisions, and some degree of veto power over decisions made outside your team that affect your work.
"Experts are not satisfied with just any abstraction, they deliberately seek elegant abstractions through which complex structures can be introduced, understood, and referred to efficiently."
Every developer I've ever talked to believed down to their core that this was what they were doing. Yet 99% of software is utter bilge.
This includes mine. I look at everything I've written 5 or more years ago, and it's all garbage. Most of the work I do is rewriting it.
There's no way around it, it takes a lot of experience to be even able to recognize a good design, let alone write one. No list of 10 rules is going to help much.
It's like painting. No "10 Habits of Famous Artists" is going to help you become a great artist.
> Every developer I've ever talked to believed down to their core that this was what they were doing.
Really? I’ve worked with a lot of people who are just trying to make the thing do the thing.
Even most of the people who are interested in good abstractions often are under such pressure to perform “I’m just shipping here don’t mind me” that they largely stop thinking about the right abstraction.
Maybe I’ve worked at too many troubled startups though lol.
It’s elegant! No, your dumpster fire of a Second System Syndrome is not elegant. Fractal designs are not elegant. Also you are in your late 30’s. Why are you just having your second system syndrome now? </oddly specific>
From Walter Bright? Sorry I don't buy this. I can accept that you might feel it could be better, but garbage? No.
Now and then I'll publish some guidelines for writing better code, and someone always points out that I violate them myself and contradict earlier dicta :-)
https://github.com/dlang/dmd/blob/master/src/dmd/root/outbuf...
I've recently fixed a number of its problems, but it's got a ways to go. Am working on it this evening.
It needs to be better because earlier today I found a bug enabled by its poor design:
I'd say, a real-world program that's beautiful 5 years later is ...something special :)
This advice is too abstract, to be practically useful in real life. ? Is it a bit like saying: To be a good runner, do this: Run fast. — Needs to be broken down into smaller more actionable steps.
Still the other advice about talking with users, and looking around at other things out there, and spending time "thinking into the future" about how one's ideas will work out, and what to not include in the software — I like that advice :- )
I'd say elegant abstractions are discovered. Those that are designed tend to have leaky corners. Some manage to rediscover something, implement it poorly, and give it a new name to confuse programmers for the next couple of decades that follow.
Experts simulate continually and fool themselves into believing they've thought of everything. Humans are able to predict the outcomes of complicated systems through heuristics. However we're especially bad at specifying invariants of complicated behaviors when it involves live-ness and safety.
Experts know when to model a system and use automation to check their designs. If that is overkill they write property tests. At the very least we write a tonne of unit and integration tests.
There's nothing like writing out the math and having the solution pop out at you.
Experts think about what they are not designing and they write it down as an invariant of their model.
This seems like a blog-vert for a book and while I love learning about how other engineers practice their craft this looks like it's going to be a mixed bag. It might help listing some of the experts involved in this project. Similar to how Coders at Work did.
The difference I think the author was trying to hit is that an expert uses abstraction to achieve separation of concerns, an important and difficult thing to do.
A junior doesn't know what separation of concerns is but they see the experts abstracting stuff and think they should abstract things then they will be like the experts.
> ...too many juniors will read this and think they need more abstraction.
Yes. And that's a good thing.The way you become "an expert" is by making ALL the mistakes yourself, seeing the results, and doing it over and over again in different contexts until you understand and can do it right (most of the time).
However I don't think we would have had Ramanujan if he were left to rediscover all of mathematics on his own. Having an expert validate your insights or point you in the right direction can speed up the learning process a lot.
So no, don't make "ALL the mistakes". Learn by observing others around you, emulating it and then finally at some point understanding it clearly enough to make something completely novel.
> ...making those mistakes but never landing most of them into production. It will boost your progress immensely if you have somebody available to critique and guide...
I mostly agree.But the consequences of what "landing a mistake in production" actually means depends on a lot on the environment and the project. Does it mean the deliverable has a hiccup and skids past the deadline a few days? Does it mean there's <gasp> a bug? Does it mean a hard-to-maintain big ball of mud that makes people miserable for years? Or does it mean a rocket blows up? All those things happen, of course, but to assign blame on any significant number of these to uppity juniors reading articles that are too advanced for them is a bit of a stretch. There are so many ways projects can fail.
I think we all can agree that juniors need the agency to try things out (hopefully with a few guard-rails installed). Sadly having benevolent mentor watch-over juniors is, in many places, a luxury and they're forced to read "articles on the internet" for guidance. It's not optimal but it's OK.
Or even better, learn by making mistakes and have great coworkers who can catch those in code reviews.
A right abstraction makes everything smaller. Most wrong abstractions make everything bigger (in the name if "flexibility", etc).
Java forces programmers to choose a point on the spectrum of "more flexibility" <-> "smaller program size". In languages like Lisp, by contrast, the more generic a function is, the smaller it is. When I choose to make a function less flexible (i.e., making it more specific to improve performance), it gets longer. The spectrum in Lisp is "more flexibility + smaller program size" <-> "more performance + larger program size", and that's almost always an easy choice to make. Every function and macro in core.clj, for example, is impressively concise.
When I write Java, I tend to go for simple first, and then have to rewrite everything 20 times as I discover what axes of flexibility I need. In Lisp, I tend to write a function once in the simplest possible way, and then re-use it in its original form forever. It's already at max-simplicity and max-conciseness by default. Except in the rare case where it ends up being a bottleneck, it's done.
What I want above all is "more maintainable". In Java, the two factors that drive this are on opposite ends of the spectrum. There's no ideal design, and whatever point I pick today will turn out to be the wrong choice later.
Suppose I have a thing I need to do, called X. I write a program to do X. Then later I need to do Y, and I realize that if I think of X as (A + B) and Y as (A + C), then if I re-organize to have one segment of code to do A, then wrap it so that B or C happen afterwards based on context (whether that be polymorphism or different scripts or procedural flow control, whatever the branching mechanism), then I've achieved abstracting A out into its own standalone piece, hopefully because A describes some independent process that makes sense to stand alone. Therefore, I've made it so that B and C's concerns can be separated from A's concerns, and X and Y are now just compositions of these nice linkable, re-usable pieces.
No matter what language you pick, if you want program X to do the same thing, the X-iness needs to still live somewhere. In Java that might look like going from two classes (X and Y) to three classes (A, X and Y). In lisp it might be three functions. I feel like in your example just now, you're comparing A from lisp to X in Java.
Please correct me if I'm wrong. I just feel like when you consider a program holistically, more genericness, and therefore more nuanced program description, always results in more text.
I agree that Java is more verbose than say, lisp or python. But I think that syntactic verbosity is really limited to a per-statement or per-block scope. I disagree that the language itself is responsible for verbosity in the higher-order composition of these pieces, weighted for, of course, how dynamic each language is. I hope you won't fault Java for not having the terseness of Ruby when Ruby doesn't have the performance or rigidity of Java.
I think you only really make order-of-magnitude leaps in reducing verbosity/excess abstraction by sliding up or down the dynamicness vs performance/safety spectrum. Tit-for-tat, I think an equivalent Ruby and Python program will be about the same size, and an equivalent Java and C# program will be about the same size.
The huge asterisk to all this is, of course, the humans actually writing the programs. Obviously a sufficiently motivated developer will be able to make an abstract mess out of any language.
Suppose you wanted to simplify the generation of contextual metadata for structured logging in an API service. The service handles requests that manipulate stored records, run logic, etc. Basic CRUD plus business logic.
The starting point is a bunch of raw calls to MDC.put() if a Java service, or an equivalent in another language[0].
An abstraction-free approach might give you a logUser method, a logUserAndAccount method, a logAccount method, a logTransaction method, a logTransactionAndAccount method, etc. This does at least simplify the actual request processing code from the starting point and make the logging consistent, but makes the program longer.
Alternatively, one could have a generic Loggable interface, with a function that returns metadata for the object to be logged, and a logWith method that takes a Loggable as a parameter. You can get fancy and provide a default implementation if all of your entities have common methods like id(). There are probably still ways to improve from here, but now instead of a dozen functions, you have one.
[0] Years ago I wrote a rubygem for this, but was not able to open source the bulk of it.
Another problem is that Java lacks type aliases. If your data have a complicated type like List<Set<Pair<Foo, ? extends Bar>>>, you have to copy-paste this type everywhere, without a way to name it succinctly.
On top of that, Java lacks type inference, even the weakest syntactic form. This is why you often need to write a long declarations, so long that they exceed the space you may have saved by factoring out a small function. Lombok and the diamond operator somehow alleviate that, but not completely.
The lack of pattern matching, or named arguments, or null vs Optional vs Result, goes without saying.
This makes Java a very verbose language, even if the logic of your code is streamlined and economical. In many standard library APIs, it is not.
(Hence Kotlin, obviously, or Scala if you can tolerate the build times.)
Java has had generic type inference since at least 7 [0], so all you need is <>
It's not perfect, but I believe it's been improved with each version.
[0] https://docs.oracle.com/javase/7/docs/technotes/guides/langu...
I'm talking about a different use case where inference via assignment does not work, e.g. a method declaration:
public List<Set<Pair<Foo, ? extends Bar>>> combine(
List<Set<Pair<Foo, ? extends Bar>>> a,
List<Set<Pair<Foo, ? extends Bar>>> b
) {...}
It would be great to have something like type Quux = List<Set<Pair<Foo, ? extends Bar>>>;
public Quux combine(Quux a, Quux b) {...}
Unfortunately, I'm not aware of any plans to introduce that.After looking briefly at the companion site for the book [0] (not much there), then the publisher's page on the book [1] (ditto) I took a look at the table of contents on Amazon [2]. Definitely there are some very interesting items there that I wish they'd used for this article, like:
* Experts sketch: [They] externalize their thoughts
* Experts work with uncertainty: [They] keep options open
* Experts test: [They] are alert to evidence that challenges their theory
Unfortunately, there are no samples of those chapters. Looks like it could be an interesting read, though.
[0] https://mitpress.mit.edu/books/software-design-decoded
[1] https://softwaredesigndecoded.wordpress.com/
[2] Click "Look Inside" https://www.amazon.com/Software-Design-Decoded-Experts-Think...
> If the title begins with a number or number + gratuitous adjective, we'd appreciate it if you'd crop it. E.g. translate "10 Ways To Do X" to "How To Do X," and "14 Amazing Ys" to "Ys." Exception: when the number is meaningful, e.g. "The 5 Platonic Solids."
Illuminating the bold ideas and voices that make up the MIT Press's expansive catalog.
>The first hint was at the footer of the page: Illuminating the bold ideas and voices that make up the MIT Press's expansive catalog.
"Illuminating the bold ideas and voices that make up the MIT Press's expansive catalog. We publish thought-provoking excerpts, interviews, and original essays written for a general reader but backed by academic rigor."
There's zero useful, actionable content. There's no evidence the authors have any big-project experience of their own, and the bibliography doesn't help make a case for their expertise.
https://softwaredesigndecoded.wordpress.com/annotated-biblio...
In fact it seems to be academic back-seat driving by a pair of authors who don't understand the difference between reading about a domain and living in it, and are now communicating in platitudes. With cutesy pictures.
"You know you could try to make your abstractions more elegant? How about focussing on the essence? Or seeing how someone else did it?"
Thanks. When our next intern slot turns up, we'll be sure to keep you in mind.
I agree. With no motivating examples these are just platitudes. Or rather, I suppose if I want the motivating examples and cases, I need to buy the book.
People familiar with Wirth, Kay, McCarthy, Iverson, Moore's (and pals) works have a fundamentally broader vision of problems and their solutions.
It's obvious, but all those points are not learnt in a discrete manner but they work as a whole and grow on you during the journey.
But really, anything you grab from any of them is awesome by default.
1. http://www.eecg.toronto.edu/~jzhu/csc326/readings/iverson.pd...
“Read? Books? With pages? And chapters? I don’t pay you to read, I pay you to program! And I’ve seen programming, programming is done with a keyboard, so I’d better hear typing! And I expect you to tell me exactly what you’re going to be doing, hour by hour, for the next two weeks, or ‘sprint’ or whatever you nerds call these things these days, and I’d better see lots of commits to the repository because I’m checking who’s committing the most because I don’t pay you to read and learn, I pay you to know and if you don’t already know there are a thousand foreigners lined up to do your job for ten cents a day so you’d better get in line, programmer!”
I just bought this cherry extraloud MX-blue so you can hear me clickyclacky from your office. And created a deeplearning model to count clicks for our new pay-by-press payroll model. something something democratization :)
I think it's mirrored vertically to further reinforce the trope from preschool of cutting out shapes from folded construction paper.
I mean, what is this. Not even a point on how Expert Software Designers get bored and browse hacker news for 5 hours?
Terrible.
I think this is one of the strongest points of a good developer, and what makes them such good problem solvers in many situations. Often you get confronted with problems, from say a client or through user feedback, which don't amount to much more than "wishes" - with no clear and/or conflicting solutions. A good developer is able to distill the problem down to its core, finds out the true nature of the issue, can communicate that back, while also offering solutions.
"I don't know nothing about databases, but X/Y can solve it because he/she is the software expert".
"I heard you are the expert in firmware, can you fix the coffee machine?"
More or less like the fact one cannot play like Mozart just because Mozart was a 'genius' (putting aside all the effort he put into it since childhood), the same applies to the 'expert'.
Edit: "The expert" https://www.youtube.com/watch?v=BKorP55Aqvg
To put it another way, I think that I would find a book about writing by Stephen King to be more informative as a budding author than, say, the dean of the English department at Harvard.
As with all things, I'm sure there may be value on many of the things they've written, but you may need to already be an expert software designer to realize what advice is useful and what isn't.
Part 2 is online: https://ocw.mit.edu/resources/res-6-004-principles-of-comput...
Good designers use good abstractions, it’s good to talk to your users... of course, there is nobody that disagrees with these things, but even the most junior of designers knows that.
What the dodo is a software designer by the way. Something from academia?
Thinking ohh that sounds weird, and then seeing it correctly. Feel bad saying it but I'm sort of disappointed now :p
I imagine the idea is that if you want to go further, you buy the book.
Seriously, aren't we over this "winners do this" bullshit yet?
Any thoughts?
they might be considered skills.
The only two skills on there that are useful are involving users, and looking around.
Elegant abstractions? Meh, so long as its simple and works.
What they are not designing? Pfft. if you can't create a clear spec, then you shouldn't really be designing
"as you are such an expert, what is missing?"
1) Communication. If you can't communicate, then you aren't helping
2) Documentation. If its not documented, then its not designed
3) consistency. Just because there are new and shiny thing, doesn't mean you should slam them in.
4) pragmatism. All choices have tradeoffs. Limit the innovation to get things done in time.
There are more, but this is all I can think of right now.
That right there is the most important one in the list. Also, as a developer, you can be the user. Try to use your design and start punching data using your designed interface. After entering 100 times you'll catch any bad crap you put there and wish for a faster or smarter way to enter it, trust me. Also, monkey your interface to your wife/kid(s)/non-technical friend(s) and they will point fast whatever they find is boring. No need to deploy in the wild to catch the bad design
I can say that hopefully never again will I work on a system where the users are remote and isolated from Dev. The benefits of dog fooding are enormous. So many small and large UI discoveries that you would never know about if the input trickled in through jira tickets.
If you want to build an elegant, user-friendly system, then use it yourself. Constantly. Repetitively. Then scratch your own itch.
Anecdotally, I've been working in the software industry for only one year (used to be in academia), but I really feel like the main challenge is collaboration and team organization. That, and tackling huge code bases.
Designing abstractions, algorithms, and all that technical stuff really is the easy part.
Edit: haha, next time I should RTFA (which clearly mentions it's taken directly from the book I remembered and pulled off the shelf). But my recommendation remains unchanged! :)
I really appreciate the thing called "abstraction" not only in Software Designer but also in general, it often takes large amount of time to be an expert in the field before one can create/design an abstraction out of the problems.
And a lot of times the most elegant abstraction is no abstraction cough oop cough. Unnecessary abstraction is as wrong as a wrong abstraction.
Many feel like you, though. I just don’t want to maintain their effluent.
Some of my approach:
-- I strive primarily to form a clear and simple mental model of the system that the software will represent.
-- Simplicity is everything in software design and should be priority one. Conversely, you must actively avoid complexity, and when part of a system is starting to feel complex, take a step back and rethink the approach. Sometimes systems must include complexity, try to contain this all into a tightly constrained area of the system.
-- Loose coupling of system components is next most important - priority two - when designing large interconnected systems - you'll succeed in designing something big if you can instead make a number of smaller systems that know as little as possible(ideally nothing) about the other parts of the system.
-- Software is like painting - you can't plan every brush stroke in advance - instead you get the broad brush strokes in place and then paint ever finer levels of detail.
-- I strive for consistency, but not slavishly. Try to use the same approaches/technologies everywhere if possible. When not possible, choose the most suitable technology/approach and use that.
-- I'm extremely hesitant to allow special cases or exceptions, but I will happily do so when it is clearly necessary.
-- I grant myself time to think about possible solutions even when there is pressure (typically from myself) to be actually implementing/coding a solution.
-- I do my best to "design out" or get rid of software - the fastest, easiest to write and most reliable code is no code.
-- I try to lean heavily on the capabilities of existing systems like database and web servers. If you really understand what existing systems are capable of at a deep level then often you can avoid reinventing the wheel and hook in to what those existing systems do.
-- I'm not a fan of complex systems such as containers and kubernetes. I have been able to build all my software architectures without them.
-- The project build should primarily aim to put together a working ended to end system that does the absolute minimum to enable the integrated whole to work - i.e. you need to build just enough of each thing to put a pig in one end and get sausages out of the other..... without much attention to anything like user interface or error handling or really much of anything else at all. This is because it is much easier to complete a system that is working, even if only minimally.
-- I'm happy to throw away previous decisions and system components if they have proven to be wrong.
About 1,000 other things I don't have time to think out right now.
less > more