On the Aesthetics of Programming
For me, programming is many things, among them a pursuit of pleasure, of comfort and beauty.
I hope this will be a living document where I add (and update) my opinions of the development of programs.
Quotations
When people who can't think logically design large systems, those systems become incomprehensible. And we start thinking of them as biological systems. And since biological systems are too complex to understand, it seems perfectly natural that computer programs should be too complex to understand.
We should not accept this. If we do, then the future of computing will belong to biology, not logic. We will continue having to use computer programs that we don’t understand, and trying to coax them to do what we want.
Instead of a sensible world of computing, we will live in a world of homeopathy and faith healing.
-- Leslie Lamport, "The Future of Computing: Logic or Biology", 2003.
Disclaimer
I'm a hard determinst, I don't believe in free will, nor do not belive in an intrinsic purpose to life. Technologically and scientifically, a fundamentalist. I'm too stupid to accept quantum mechanics, and too much of a simpleton to understand the eudaimonic. With this in mind, it shouldn't be a surprise that my approach to the craft of describing programs is also fundamentalist in nature, in other words, first-principles all the things! If you don't understand something from the ground up, you might be able to use it, but not well. We must of course accept that we can't understand everything in the universe from first principles, but we should strive to understand as much as possible, because we can make better use of that which we understand deeply.
This opinion was formed many years ago when I worked on a little space-ship game that I never released, I had two 2d points and I needed to know the position of a point N length into between them on the X axis. I am not good with math or numbers, I have no training in it beyond public scool, so I didn't immediately recognise the relevance of the pythagorean theorem for finding the solution. Instead I sat down and thought about it until I arrived at a solution. It was of course a simple problem, and I'm sure everyone can solve that if they want to, but that's not my point. I went online to see how to "really" do it and I saw that my solution was identical to a well known formula for finding the slope of a line, called"rise over run". This helped me understand my line-related problems on a very fundamental basis, even though others had done it before me, I had "identified and invented" the math I needed to solve my problem.
I strongly believe this approach of first inventing one or more solutions from first-principles, from nothing but what you have in your head, without looking for what others did before, and only later look at the "right" way to do it, gives a better understanding of the problem and allows you to arrive at more elegant solutions; of course the most elegant solution won't often be your own, but the process will lead you to a more coherent and elegant program overall.
Things dislike
- and am guilty of myself sometimes
Dogma and rigidity of thought
Unyielding adherence to paradigms and patterns is not good taste. Good taste identifying when a pattern has emerged in the system, and, then consider if this pattern exposes some advantages which are suitable for simplifying (refactoring) the existing implementation or allowing the solution of new problems.
If you're attempting to solve problems by fitting them into patterns and paradigms, you're doing it the wrong way around. For the vast majority of problems, solving them is the easy part. Understanding what the problem actually is, so it may be broken down into constituents and then finding the set of possible solutions with the most synergy, is the challenging part. Having correctly identified and laid out the problem allows it to be solved in implementation. At this point, one might start thinking about patterns, but it is not yet time to look through, lest you by accident force your problem into one of them (this is a mistake, you risk changing your problem before it's solved, and then you solved the wrong problem!).
The right time to look for patterns is maybe not that obvious, when you think about the word, pattern. It is something one recognises in what is. Before a problem is solved, nothing is.
The right time to look for patterns is thus, when the problem has been solved, it's highly likely that there are already one or more patterns or proto-patterns in your solution, only after having arrived at a working solution can you consider whether refactoring into formal patterns can be done without changin which problem you're solving.
Names: id, type, date, time
We tend to do this all the time, but these general words do not belong in data structures, it's the modern world, we have good keyboards and large harddrives, and the compilers and databases can handle long variable and column names.
It seems obvious, you have some structure which you want to identify, so you give it an id. For example, you have structure called User { name, birthday, email }, and you want to refer to him by something else than their name, because people might change their name over time or there may be multiple people with the same name. Names are also not the best candidate for a very fast lookup in a database table. So you want to give them an Id. User { id, name, birthday, }, thee system works, everyone is happy. Time goes on, requirements are added: Users belong to organisations, so we model it using our previous knowledge: Org { id, name, email }.
Of course now people should belong in organisations, database-wise we probably use a relation table, that relates User.id to Org.id, but when reading out a user we might want to include stuff about their Org too, so we do a little backwards-comaptible remodelling: User { id, name, birthday, email, orgId, orgName, orgEmail }, and now we want to destructure stuff, because our langauge provides this nice syntactic sugar.
Destructuring the User object ironically leaves us with all the variables being related to the user having weak names, while all the extra stuff about the organisation, having descriptive names. It gets only uglier the more different stuff we wrangle at one time, we end up in situations where we can't destructure tow different types of objects because their field names collide (this can't be avoided entirely, but we get far by avoiding to have nonspecific version of the most common names: userId is thus better than id, even if you think user.userId looks silly). Descriptive names is one ingredient which reduces cognitive load, which allows us to solve more complex problems in less time and space. In a sense, this extra comfort we gain by having names that destructure nicely, increases our local capacity for complexity, meaning that we may postpone or entirely avoid breaking up or refeactoring in order to solve the problem at hand or add additional functionality quicker and cheaper in the future (before the inevitable refactor, no one approach can be both elegant and infinitely expandable).
Overly rigid "beautifiers"
Source code carry information by its keywords, and it is true that the compiler derives nothing further from its presentation. However, humans do, and the shape of the program can inform us of it's intent, of which parts are trivial and which parts are subtle. Well laid out code tells where the critical points are, where the hot loops and the expensive algorithms are, it informs of where careful thought must go, and where it is easy to expand. Code formatters that impose strict mechanistic formatting sets the lowest common denominator, and while they may make it easier to read the code, they can also make it harder to understand it.
A good example is the typescript beautifier at the time of writing, it won't merely makes sure you have the right amount of spaces between your parenthesis and your commans, but it will gladly re-write your beautfiully composed poem into something resembling a demented assembly listing. Rather than let you shape your code in a fashion that conveys subtle differences, everything is treated the same. It's "just code" and the compiler dosen't care.. This is true, but then who is the beautifier for, if not the writer ?
A good beautifier will gently create consistency, by following the example set by the program text. It will add or remove a space so that a parenthesis is symmetrical, it will remove trailing whitespace, it might even, and I'm not sure if that is a good idea, space tokens evenly, if it can detect that their spacing free of intent. Such a beautifier is probably impossible to create. But then again, maybe programs shouldn't be described in only in plaintext.. Maybe programs should be described just slightly more intelligently, so that the tools for creating them could do something more intelligent with their presentation. Maybe the presentation and the content of a program shouldn't be so tightly coupled at all. But for now it is, so to make the best of it, have some dicipline yourself in setting up the program text in a way that is pleasing and intentional.