On the other hand, finding an opportunity to introduce a common interface to a familiar codebase feels like leaving behind a mental burden. Sounds crazy I know, but introducing order into a chaotic mess is just relaxing to me, especially when I know I will have to maintain it.
It's mathematical interfaces.
Commutativity, Identity, Associativity, Ordinality and more. For these interfaces, it becomes less about "designing" interfaces with gut intuition, guessing and checking... but more about finding the final design via calculation.
Math is known to be universal so it makes sense why mathematical interfaces have such wide application. When you use mathematical interfaces and compose everything along those parameters... it's no longer about "you never come up with the right interface at first"... That concept becomes less relevant.
It's hard to agree with me when I'm just saying it here. It has to "click" after you tried it with a language that supports this type of programming first class.
I think that the sad truth is that ugly code reflects an impedence mismatch between the model in the codebase and the reality of how that software would be used, which wouldn't be obviated by taking a more formal or calculated approach to constructing the code. I've seen bad code written in every language and good code written in most languages. Moving state mutation to edges just lifts and shifts the hard part, it doesn't make it any easier.
But to conclude this dissent with a point of agreement, I that the style of programming you're talking about already exists and is common, is called SQL, and is built on very concrete primitives in set theory. You get ACID properties which give transactional isolation, at-rest data integrity based on normalization and uniqueness constraints and purely functional data transformation that can live in a purely functional language (SQL) rather than inside whatever application level language is chosen. And for what it's worth, I have seen enormous, universal improvements in codebase quality by "lifting and shifting" computation that could more comfortably and ergonomically live inside SQL to occurring there.
To your point, I've seen this occur much more successfully when utilizing an ORM approach that is more functional (query builder flavor) than imperative (object mapper flavor).
Is this really the case? Or what do you mean by this exactly? Gödel’s incompleteness does apply to it as well, and we can for example only determine the Busy Beaver number up to a fix point no matter what. Though it probably doesn’t matter from a practical perspective, I would just like to know in what sense do you mean universality.
You never come up with the right medicine at first because there is no right medicine. There are better medicines, but you usually need to encounter more outcomes to find it. Balancing up front time designing a medicine with velocity to test it is a principle problem of medicine development.
Thank you for your template
(There are some exceptions, that have to do with notes eg for security implications, or optimization techniques, or side effects. And also to document parameters in duck-typed languages. But even those should be put into structured comments, like DocBlock or YUIdoc or whatever kids use these days.)
I have learned this with time. Like when you are procrastinating and putting off doing something, that’s a sign you need to attract partners.
I would encourage you to ponder this a little more. I work on scientific code bases and there's just some processes and math that requires line by line exposition beyond what the code can offer.
There's also nothing worse than digging into a library to view the code and just being left with a ton of small classes and no notes on how it's all meant to interact. Comments here and there are helpful.
In some domains like scientific or high performance computing they are absolutely necessary to a) provide context to others b) make you not forget c) prevent unintended consequences and regressions.
You reorder some operations or do seemingly weird things for improved performance, numerical stability, or address some not easily testable bug? If you don't comment it, this knowledge will be lost and someone might obliterate it during a refactor.
Sometimes you need comments explaining an API that you are using, because its workings are not immediately obvious. Then the smell is not coming from your code but from the code you're calling.
This could not be more wrong.
Not everything is easy. If a library is addressing a complicated domain, solving by definition a complicated problem, it is fine if it requires some learning.
When did expertise and learning become bad things? If software is an engineering discipline, why would people in it ever promulgate the idea that any random cog can step in to any “engineer”s shoes?
Rich Hickey analogizes this mentality to the world of music, where it taken for granted that learning an instrument requires a lot of study:
“ We start with the cello. Should we make cellos that auto tune? Like, no matter where you put your finger, it's just going to play something good, play a good note.
“[Audience laughter]
“Like, you're good. We'll just fix that.
“ Should we have cellos with, like, red and green lights? Like, if you're playing the wrong note, you know, it's red. You slide around, and it's green. You're like, great! I'm good. I'm playing the right song. Right?
“ Or maybe we should have cellos that don't make any sound at all. Until you get it right, there's nothing.
“ [Audience laughter]”
https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
If something can be made easier without undermining its integrity, great. Not everything can be made as easy as drinking from a cup, something most 3 year olds can handle. If you think hitting dot in your IDE and choosing among the options is as much as you should be required to learn, you are asking to use NERF toys instead of power tools. Sometimes you need to read things, welcome to adulthood.
Honestly, the best way to achieve the latter is probably to encourage the former.
(Which will never happen as long as companies prefer to hire N interchangable people than M well-trained people, even with M ≪ N.)
The innovation of discrete pitch was practical -- for many songwriters, the point is to get out the song and not focus on "implementation details" -- and I think there are a lot of similarities there to software.
It depends. It's really great when that does work out. It's traditionally my first step (copilot frequently beats me to the correct use of an unfamiliar API today), second step being the documentation if that's not enough. Step 1 is almost always sufficient.
What are interfaces if not ways to communicate?
Basically, the opposite of a modern microwave.
Rico called this the Pit of Success. That concept really resonated with me. More generalized, it is the key point of good API design. We should build APIs that steer and point developers in the right direction. Types should be defined with a clear contact that communicates effectively how they are to be used (and how not to).
This concept is also tied closely with the concept of "making illegal states unrepresntable," popularized by Yaron Minsky[2].For example, a document workflow system might naively model Document as a single entity that has all possible fields for all possible states. This could result in the need to raise exceptions (or return error codes) when the Document state is invalid, e.g. you can't approve a draft document until it's submitted. A better API models each state separately (e.g. using a union type, if your language supports it); so you'd have `DraftDocument`, `SubmittedDocument`, `ApprovedDocument`, `RejectedDocument`, where only `DraftDocument` would offer a `submit` method, and `SubmittedDocument` offer a `approve` and `reject` methods, returning an `ApprovedDocument` or `RejectedDocument`, respectively.
[1] https://learn.microsoft.com/en-gb/archive/blogs/brada/the-pi...
Sounds like "you should wear a straightjacket because you might fall down when running with scissors" kind of a solution.
I think the example was pretty bad, however the union type being referenced is that matrix, afaik.
Let's start with a different example. I have a business process that runs off a plan-document. The plan-document includes things like caps for number of times things can happen over a period (including, all-time), or day parting specifying when things can happen at all (only saturdays), and absolute controls like "active" or "inactive", on top of the creation/deletion paradigm.
When the process runs, what is the state of the business process at any given time? "inactive because capped out" or "inactive because day parting"? This set of labels will grow, in permutations, over time as well as discoverable business needs. eg Was it "inactive" because it was created that way or someone manually deactivated it with an update? Now the program needs to reference a history of changes as well as referencing run-state.
A business that is building a new product, especially within a domain that few people understand, requires more than building a set of states and assume they will always meet the needs. This is a recipe for lots of large-scale rework and bugs. A set of states (be it a bitfield or json blob or whatever) fed into a rules engine (or component) will likely be more extensible over time than looking at simple labels.
Granted, this is predicated on the software being a non-trivial system.
No, you'd use polymorphism. As a silly example (but it demonstrates the technique), look at the phantom-typed builder pattern, where you can set the fields in any order you like but you can't build until you've set all of them.
It's a shame too many developers think this huge idea is just the interface keyword, or OOP, or even just the `object.method()` syntax. I hear dot-autocomplete come up in conversation almost every day now. When I was in college people asked me "How can you even use C? It doesn't even have classes...?", the implication being you couldn't encapsulate your code at all, and I get the same shrinking feeling when people talk about dot-autocomplete as if it's synonymous with interface discoverability. But it's really just one particular implementation (heh) of a much broader and more abstract (heh) idea. It's like calling all tissues Kleenex or all sodas Coke.
Just talking about it in terms of functions, you usually have at least one argument. Then you’re trying to find the right functions and remaining arguments. Single dispatch and the dot syntax support this very well! I’ve never seen it, but maybe you could do the same thing with functions. Just put in the first arg, maybe more, and then the function name.
It's a fallacy to assume a perfect interface needs no documentation, just as it's a fallacy to assume perfect code needs no comments. Most interfaces are far more complex than a coffee mug. In reality, the more complex something is, the more documentation is needed to describe how it works, how to use it, how not to use it, and why it works that way.
The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise. — Edsger Dijkstra
> 15. (Shea's Law) The ability to improve a design occurs primarily at the interfaces. This is also the prime location for screwing it up.
While this is about hardware interfaces, software interface fulfill the same exact role, and the same exact principle applies. The design is in the interface. The implementation is just grinding until it's done, because the decision of how something will be implemented are already determined by the interfaces and the information we have on the infrastructure we'll be doing the implementation in.
Good interfaces are retroactively obvious. Not like my modern microwave.