Fred Brooks (author of The Mythical Man-Month, an awesome book on software development) wrote another, lesser-known (but also great) book called The Design of Design. It’s broader in scope, less focused on software architecture and more on the process of design. (BTW: the terms “design” & “designers” are used in a very broad sense here)
He does not prescribe any faddish low-level process (“scrum! flat design! agile! stand-ups! story points and post-it notes!”) but rather walks you through examples of the many mental foibles that designers may stumble over.
In pointing these out, he pushes you to be more reflective about your own habits and basic assumptions. For instance: consider the value and handling of brainstorming, which is typically just assumed to be valuable. Are the ideas produced actually better? (Actually, individuals outperform groups in the quality of ideas in their originality, feasibility, and effectiveness) Do the results tend to be better when you remove criticism from the conversation? (You sure do get a greater quantity of ideas and can reach much wilder solutions than if you allowed the naysayers to shoot ideas down prematurely)
My favorite chapter, I have to say, is the one entitled “Esthetics and Style in Technical Design”. In it, Brooks explores that fuzzy idea of what makes for a “beautiful system”. People can talk about beauty for cars, airplanes, houses, sculpture -- where the object is physical and has visual beauty. But people judge totally abstract things as being beautiful too: a theorem, a programming language, even a “visually ugly” but “logically beautiful” system such as Minecraft. Visual beauty is pretty apparent; so you might wonder, what does it really mean to be bear “logical beauty”?
Brooks lays out the prime attributes that define a logically beautiful system. I’ll list them briefly here:
The property of being able to accomplish a great deal with few elements. This is positive only up to a point, so beware of following this to the extreme: excessive parsimony leads to the use of non-obvious idioms. You can design a language with very few elements that’s completely insane to actually code in. You can have an API that only allows incrementation (but with negative numbers): parsimonious, yes; intuitive, no.
There must be a direct path from what you want to say to how you say it. The basic structural concept is plainly evident and if not obvious, then easily explained. Familiar and simple metaphors aid in this (e.g. the "Desktop" of Macintosh, the "spreadsheet" of VisiCalc, the "blocks" of Squarespace).
Given familiarity with just part of a system, you can predict the rest of it. You'd expect all functions in a package to throw similarly formatted exceptions, use similar error codes, take similar input types. If the $increment function takes a path and number, so should the $decrement.
Do not link what is independent. A change in one function does not affect another. In other words: no side effects. Air conditioning in your car shouldn’t affect the lighting (though in reality, at a certain point all these systems draw from the same power supply and in extreme situations, orthogonality breaks down)
Do not introduce what is immaterial. Do not expose incidental complexity, where extraneous implementation details affect the interface. Shifting of gears in a car is completely incidental and do not belong in its interface. An example of an egregious violation of propriety: iPhones being unable to use Facetime unless connected to WiFi. This limitation is completely arbitrary from grandma’s perspective.
Do not restrict what is inherent. Generality is the ability to use a function for many ends. Designs should not make too many assumptions about the use of a function. This is where Unix really nails it, keeping standard i/o, pipes, and files completely general.