5 ms·
Excellent observations. The essay illustrates what it is advocating by the generous clarity, simplicity, and accessibility with which it is written. John Oste
by gregfjohnson 5y ago
Excellent observations. The essay illustrates what it is advocating by the generous clarity, simplicity, and accessibility with which it is written.
John Osterhout wrote what may be the best elaboration of this perspective in his wonderful book "A Philosophy of Software Design."
A couple of related thoughts from an old grizzled programmer (moi ;-), gregfjohnson.com):
Rule number one: All code must follow the Porthole Principle. The fundamental issue in programming is that we each look out at the world through a mental "porthole" that limits our field of view. We are limited in how much we can see and understand at any one time. Therefore, systems must be structured so that they can be understood completely, at all levels, even though we are only allowed to investigate them piecemeal, through our own limited and finite cognitive windows.
Abstraction is the essential tool that allows us to create systems of arbitrary size and complexity. A beautifully designed abstraction is easy to understand and use. It is so trustworthy that you don't feel any need to worry about how it is implemented. Finally, and most importantly, it enables you to reason with rigor and precision about the correctness of the code you are writing that makes use of it.
- berkes 5y agoMy rule of thumb is that 'the smartest code, is the code that you can work on in your dumbest moments'. Or, in a team, 'the most senior code, is the code where the most junior dev can easily work with'. They may seem obvious. But in practice, far too often do I encounter code that is far too complex, and fails your porthole principle, and extremely hard to reason about, or work in, without worrying. Now, when such code is made by an inexperienced dev, this is fine, and acceptable: finding the right abstractions, or even seeing the need for those, comes with experience. But when senior devs produce such poorly abstracted, hard to reason, complexities, it is inexcusable. It is your task to make complex problems simple. To make unreasonable domains reasonable. To apply the right abstractions in the right places. To make code that you can work on when tired, distracted and stressed. To build structures where a junior can feel at home in, in hours.
- valenterry 5y agoNice quote, but I would refine it a bit: 'The smartest code is the code that has your back in your dumbest moments and boosts your productivity in your best moments'
- rob74 5y agoThen again, another form of humility is recognizing that the code you write yourself may also be hard for other people to understand, and code from other people may be harder for you to understand because of different programming styles, but you should still approach it with an open mind...
- jstimpfle 5y ago> Abstraction is the essential tool that allows us to create systems of arbitrary size and complexity. A beautifully designed abstraction is easy to understand and use. It is so trustworthy that you don't feel any need to worry about how it is implemented. Finally, and most importantly, it enables you to reason with rigor and precision about the correctness of the code you are writing that makes use of it. Heh, I don't think there are a lot of proven abstractions out there. And most so-called "abstractions" are in fact not. Let me explain using two popular abstractions, ones that are so low-key that they wouldn't even considered by most. 1) Function calling. This is an abstraction that abstracts away the logistics of setting the state in a process in the right way such that the next piece of code can continue. When that code finishes, it will leave a well-defined state as well (first, in terms of the call stack, but maybe a return value as well). Now, I contend that function calls can be the wrong abstraction in many situations, at least more often than people think, and many many functions seen in the wild should never have been created. Functions remove a lot of control where values are stored, and the storage locations can't be shared across functions, which results in a lot of nested calls that tunnel through many of the received arguments - instead of just leaving them in a common, predefined place. Also, lots and lots of functions in practice tend to amass local state, and as it is with the tree-shaped nature of function calls, once you give up control, that state is gone, so you'd better clean up after yourself. This is in fact a great burden, and very complicated systems arise to "solve" this problem, like constructors/destructors, try/catch, functional unintelligible academic theory (Monads), and other resource tracking systems. 2) Streams. In my mind, they are arguably one of the most successful abstractions ever created. A stream abstracts one of the endpoints of a unidirectional connection (the read or write endpoint). You use it by just saying "read" or "write", and very little of the complexity of interacting with that other endpoint shines through - lossyness of the connection, flow control, packet size, error handling... streams don't provide access to most of these. This can be a very unhappy situation to the point where the code that deals with that stream would be much better off just dealing with the underlying connection directly. A huge aspect of this is flow control and scheduling - we mostly act like transmission was an atomic operation, but there is a lot of logistics involved. To hide this, OSes invented blocking interfaces, putting your process to sleep until the operation is completed, or providing simplistic non-blocking interfaces. All of this is very bad for performance and predictability, especially blocking interfaces.