Hacker Newsnew | past | comments | ask | show | jobs | submit | Warwolt's commentslogin

I found "A philosophy of software design" to be a well intended but somewhat frustrating book to read.

It seemingly develops a theory of software architecture that is getting at some reasonable stuff, but does so without any reference _at all_ to the already rich theories for describing and modeling things.

I find software design highly related to scientific theory development and modeling, and related to mathematical theories like model theory, which give precise accounts of what it means to describe something.

Just taking the notion of "complexity". Reducing that to _just_ cognitive load seems to be a very poor analysis, when simple/complex ought to deal with the "size" of a structure, not how easy it is to understand.

The result of this poor theoretical grounding is that what the author of A Philosophy of Software Design presents feels very ad-hoc to me, and I feel like the summary presented in this article similarly feels ad-hoc.


> Just taking the notion of "complexity". Reducing that to _just_ cognitive load seems to be a very poor analysis, when simple/complex ought to deal with the "size" of a structure, not how easy it is to understand.

Preface: I'm likely nitpicking here; the use of "_just_" is enough for me to mostly agree with your take.

Isn't the idea that the bulk of complexity IS in the understanding of how a system works, both how it should work and how it does work? We could take the Quake Fast Inverse Square Root code, which is simple in "size" but quite complex on how it actually achieves its outcome. I'd argue it requires comments, tests, and/or clarifications to make sense of what its actually doing.

How do we measure that complexity? No idea :) But I like to believe that's why the book takes a philosophical approach to the discussion.

I agree the arguments in the book largely "make sense" to me but I found myself finding it a little hand-wavey on it actually proving its points without concrete examples. I don't recall there being any metrics or measurements on improvement either, making it a philosophical discussion to me and not a scientific exercise.


I mean, we can definitively talk about simplicity/complexity in a fairly easy way when it comes to mathematical structures or data structures in my opinion.

For instance, a binary tree that contains just a root node is clearly simpler than a binary tree with three nodes, if we take "simple" to mean "with less parts" and complex to mean "with more parts". Similarly, a "molecule" is more complex than an "atom".

This is a useful definition, I think, because when we write computer programs they always written in some programming language, with a syntax that yields some kind of abstract tree, so ultimately we'll always have _some_ kind of graph-like nature to the computer program, both syntactically and semantically, and surely graphs also permit the same kind of complexity metrics.

I'm not saying measuring the number of nodes is _the_ way of getting at complexity, I'm just pointing out that there's no real difficulty in defining it.

Complexity means more stuff, and we simply take it as a premise that we can only fit so much stuff in our head at the same time.


I think my issue with this generalization is assuming the code itself is where complexity is measured and applied.

For example, the Quake Fast Inverse Square Root[1] takes into account nuances in how floating point numbers can be manipulated. The individual operations/actions the code takes (type casts, bit shifts, etc.) are simple enough, but understanding how it all comes together is where the complexity lies, vs just looking at the graph of operations that makes up the code.

Tools like Rubocop for Ruby take an approach like you mention, measuring cyclomatic and branch complexity in your code to determine a mathematical measurement of the complexity of that code. Determining how useful this is, is another conversation I think. I usually find enforcing rules around that code complexity measurement against your code to be subjective.

Going back to the article, the visualization of with vs without abstractions can cover aggregating the mathematical representation of the code and how to tackle complexity. Abstractions lets you take a group of nodes and consider them as a single node, allowing you to build super-graphs covering the underlying structure of each part of the program.

> both syntactically and semantically

I do want to cover semantic program complexity at some point as a deeper discussion. I find that side to me to be quite interesting. How to measure it too.

[1]: https://en.wikipedia.org/wiki/Fast_inverse_square_root


While the tools you talk about sound interesting, to me this was more about an in-principle possible measurement rather than something we'd actually carry out.

I think stating that "more stuff" in the program code and in the spec leads to more stuff to keep track of, and so we want to minimize complexity to maintain tractability?


I think that's reasonable :) More stuff is more stuff, no matter how simple/complex the aspects of the code and reasoning for why it is that way.


If this is all so easy and obvious, why do all of the tools/metrics that measure software complexity suck?


Ease of definition doesn't equate ease of measurement..


> related to mathematical theories like model theory, which give precise accounts of what it means to describe something

Perhaps too precise? APoSD is about the practical challenges of large groups of people creating and maintaining extensive written descriptions of logic. Mathematical formalisms may be able to capture some aspects of that, but I'm not sure they do so in a way that would lend real insight.

"How can I express a piece of complicated logic in an intuitive and easy-to-understand way" is fundamentally closer to writer's craft than mathematics. I don't think a book like this would ever be mathematically grounded, any more than a book about technical writing would be. Model theory would struggle to explain how to write a clear, legible paragraph.


I think model theory is a really good source of theory to ground the notion of modules.

The relation between an interface and an implementation to me is very much the same as between a formal theory and a model of that theory.

I agree that in practice you'd want to use heuristics for this, but I think the benefits would be similar to learning a little bit of formal verification like TLA+, it's easier to shoot from your hip if you've studied how to translate some simpler requirements into something precise.

For a book like this you'd probably not need more than first order logic and set theory to get a sense of how to express certain things precisely, but I think making _reference to_ existing mathematics as what grounds our heuristics would've been beneficial.


I haven't read it myself but I probably will because I have a lot of hope for this topic (there must be a better way to do this!)

I worry that it doesn't much matter if it's perfect or mediocre, though, because there's a huge contingent of project managers who mock _any_ efforts to improve code and refuse to even acknowledge that there's any point to doing so - and they're still the ones running the asylum.


Project managers shouldn't be running engineering. They are there to keep the trains running on time, not to design the track, trains and stations.

The generally accepted roles are Product decides what we need to build, Design decides how it should work from user perspective, Engineering decides how to build it at a reasonable upfront and maintenance cost. This involves a fair amount of influence, because Engineering is better equipped to describe the cost tradeoffs than any other function. Of course this comes with the responsibility of understanding the big picture and where the business wants to go. IMHO you should not be speaking to project management about code quality, you should maintain ground level quality as you go, for bigger refactoring/cleanup this needs to be presented to Product leadership (not project managers) in terms of shoring up essential product complexity so it's easier for customers to use, less support, and simpler foundation for the next wave of features. Never talk about code with non-technical stakeholders.


I disagree fundamentally with the modern division of labor. I’ve been around long enough to understand that it doesn’t actually have to work like this.

I don’t think you can be an expert in generic “Product” just like I don’t think you can be a generic management expert.

And I don’t think you can decide what to build or how it should work from a user perspective without taking into account how it’s built. In many ways I think how it’s built tends to inform what it should do more than the other way around.

However, Product alone is never the cause of bad software in my experience. It’s always product plus an engineer who refuses to push back on the initial proposal.

In most cases when product and design comes to you with a feature, and all the solutions you can come up are going to add tech debt or take forever, you should step back and talk to Product about the problem they are actually trying to solve.

If you go back to product with “I can build this very similar feature that will get you 90% of the way there, but will take 1/2 as long and not create maintenance problems down the line”, they will almost always be happy with that.

The real problems are caused when an engineer says immediately “yep I can build that in 2 weeks” and starts trying to force their solution through by telling everyone that product insisted on this specific feature in this specific timeline that unfortunately can only be done in the way they’ve designed. And then they tell product that they have a solution but are being blocked.

I’ve seen this pattern over and over.


Agree on the one engineer overpromising. But you can talk about a product without knowing how it’s built in finer details. But what can and can’t be done is the realm of engineering. Then the filtered list can be reduced by product to the ones that are inportant. So it’s actually a spiral: (product) here’s what I would like -> (eng) here’s how it can be done -> (product) let’s go with this one -> (eng) here is the plan -> etc…


This is exactly what I assume when I see people blame their tech debt on others.

We do the work, we are responsible for whatever it is. Sure maybe some times you begrudgingly just have to do something you're told, but in my experience there's almost always room for discussion and suggestions. I think most devs just don't care. They do what they're told and blame others if it's ass.


I should've noted that, although I found it frustrating, I think it's a good read for most programmers. There are many excellent ideas in the book.


The author is describing less a theory and more a framework or system of heuristics bases on extensive practicap experience. There's no need for rigor if it's practical and useful. I think your desire for grounding in something "scientific" or "mathematical" is maybe missing the forest for the trees a bit. Saying this as someone with loads of practical software development experience and loads of math experience. I just don't find that rigor does much to help describe or guide the art of software. I do think Ousterhout's book is invaluable.


My issues stems from me feeling like a lot of terminology introduced by the author ending up being used in different ways in different paragraphs.

It didn't feel like a thought through whole, and I felt somewhat punished for trying to read along attentively.

I also found there to be a frequent conflation of e.g. the notion of modules and a classic OOP-class, to me it seemed like the author thought of them interchangeably.

To me there's enough theoretical computer science that can be used to help ground the terminology, even if it's just introduced cursory and with a reference for further reading. But at least then there'd be more consistency.

I'm not sure I think the book is invaluable, but I think it's a good contribution to the subject.


That's very interesting. Can you recommended any resources for learning more about this?

Also, have you considered writing on this subject yourself? I get the feeling that your perspective here would be valuable to others.


I'm very mathematically inclined, so I would probably want a "proper" treatment of this subject to include both formal logic, set theory, type theory and model theory, but they're also subjects I'm still familiarizing myself with.

My basic pitch is that, to a large degree writing sensible computer programs is about modeling some real life activity that the computer program will be part of, and describing things accurately has been done in other fields than programming for many hundreds if not thousands of years, so there's a deep well to draw from.

Despite my appetite for a dry and mathematical treatment of writing computer programs, I still think the book is good for what it is. I think I would go easier on the book if it were not for the title, because philosophy is precisely one of those subjects that tend to favor being very precise about things, something I distinctly think the book lacks. What the book is, however, is an excellent _sketch_ on what we'd want out of program design. I definitely agree about the author's notion of "deep modules" being desirable.


See e.g A Field Guide to Complex Systems , https://www.bm-support.org/problem-solving-methods/


I would also be interested in any book recommendations you have.


Looks nice! Is there any plans on a language server and formatting tooling?

Usually I feel like that's bare minimum before I'd like to try and play around with a language


When a bug like this can cause real world harm, we can't just bumper car program our way out of things. As engineers we should be able to provide real guarantees.


I do agree for safety critical systems, you’re right.

But I don’t think we are engineers. Software dev isn’t like engineering. You can’t change the structure of a bridge after it has been built by deploying code to prod in a minute. Software dev is just software dev, it’s not engineering or science. It has some parallels with craftsmanship, but it’s unique.


Types give you static proof where tests only give partial inductive evidence. I cannot _fathom_ why people would prefer tests over types where types do the job, outside anything but sheer ignorance.


Isn't this just the newtype pattern?


It is. To some it is more fun to reinvent the wheel than to study history


Well, "newtype pattern" is itself a reinvention of a decades old concept. Presumably "newtype pattern" gets its name from the `newtype` keyword in Haskell, which was introduced in Haskell 1.3 (1996).

To the best of my knowledge, the earliest specific term for the concept is painted types (Simonyi, 1976)[0], but I believe this was a new term for a concept that was already known. Simonyi himself quotes (Hoare, 1970)[1]. Hoare doesn't provide a specific term like painted type for the type being defined, but he describes new types as being built from constintuent types, where a singular constituent type is known as the base type.

Simonyi uses the term underlying type rather than base type. Hoare eludes to painted types by what they contain - though he doesn't explicitly require that the new type has the same representation as it's base type - so they're not necessarily equivalent, though in practice they often are. Simonyi made this explicit, which is what we expect from newtype in Haskell - and specifically why we'd specifically use `newtype` instead of `data` with a single (base) constituent.

If you're aware of any other early references, please share them.

---

[0]:https://web.archive.org/web/20170313211616/http://www.parc.c...

> These examples show that the idea of types is independent of how the objects belonging to the type are represented. All scalar quantities appearing above - column numbers, indices and so forth, could be represented as integers, yet the set of operations defined for them, and therefore their types, are different. We shall denote the assignment of objects to types, independent of their representations, by the term painting. When an object is painted, it acquires a distinguishing mark (or color) without changing its underlying representation. A painted type is a class of values from an underlying type, collectively painted a unique color. Operations on the underlying type are available for use on painted types as the operations are actually performed on the underlying representation; however, some operations may not make sense within the semantics of the painted type or may not be needed. The purpose of painting a type is to symbolize the association of the values belonging to the type with a certain set of operations and the abstract objects represented by them.

[1]:https://www.cs.cornell.edu/courses/cs4860/2018fa/lectures/No...

> In most cases, a new type is defined in terms of previously defined constituent types; the values of such a new type are data structures, which can be built up from component values of the constituent types, and from which the component values can subsequently be extracted. These component values will belong to the constituent types in terms of which the structured type was defined. If there is only one constituent type, it is known as the base type.


To be fair, the fact that the IO Monad is in fact a monad is a sort of quality of life solution. Monads themselves don't have any side effect implications


Something about the website is melting my phone while trying to read the article. I think it's maybe those animations? Couldn't finish reading because the tab froze


It's a quote that originated as a paraphrase of Einstein about applying Occam's razor to development of scientific theories.

Things should be as simple as possible, with respect to your criteria. E.g. a scientific theory should posit as few objects as possible while still being able to explain all observed phenomenons.


I read this book with some colleagues at a work book club, and I think it's interesting how split the opinion on the book is among readers.

My impression is that there's some good ideas in the book, but it suffers from not being thorough enough on a theoretical level. Many definitions given are NOT consistently used, the book frequently falls back on discussing things in a very OOP centric way, and a lot of stuff came across to me as just opinion pieces.

Some stuff I found was excellent, like the notion of module depth.

When reading reviews on Goodreads, there's a similar disparity between people who really liked it and people who are critical of it.


> the book frequently falls back on discussing things in a very OOP centric way

I do procedural most of the time. Do you think I can still benefit from reading the book? Judging from the summary in blog post, it seems to be a nice read even for non-OOP code. It's just my first impression though.


Yes, it's a pretty good book. I'm somewhat annoyed at the book _sometimes_ speaking in general terms applicable to any paradigm, and then sometimes speaking in very OOP centric terms. It's still a worthwhile read.


Your own words betray you.

Pinning down the details of your requirements is _exactly_ the act of choosing the correct level of abstraction. When picking out what matters for correct behavior, and what doesn't, you are defining an abstraction.


Sure, but abstraction is not in any way the same thing as architecture (which is what you were talking about), and it's not structure (which Bartosz Milewski was talking about).

And besides, Category Theory is mostly concerned with finding similarities between different mathematical objects, when you look at them abstractly and ignore some details. It helps you find that numbers with adding are homomorphic to functions with composition. But programming is about telling a computer how to actually add 156 with 1571 by adding bit to bit, and how to compute the composition of two functions by computing the result of the first and putting it in a register so the second one can take it as input, which CT doesn't concern itself and no category theorist cares about in the slightest.


> programming is about telling a computer how to actually add 156 with 1571 by adding bit to bit

programming can also be about producing generic solutions that commit to some minimal set of assumptions so that solutions work across a variety of contexts, e.g. regardless of whether your data is floats or ints.

sometimes good engineering is about finding decisions you can avoid making or thinking about. Sometimes you have to decide how to add the numbers bit by bit, but for that particular example, it's even better to not care about how the numbers are added up (which is a detail of the CPU or a bignum library or whatever).


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: