<@UAVCC2X70> I think you are over estimating the a...
# thinking-together
w
@Dan Cook I think you are over estimating the amount with which new representations can make code clearer, or at least, we haven't found such representations yet. There is plenty of super complex code out there that even a well meaning, talented programmer couldn't make simple, be it by "decomposition and abstraction" as you say (which are very limited in their power), or by using better representations of the code. The real complexity is in the "logic" (for want of a better word) of the code, and we have no silver bullet to simplify it.
d
I only agree with the part of your statement that says, "we haven't found such representations yet." And I think we have to keep trying. And it's a faith-based argument because you can't prove it until you can prove it. Also, I think the problem can be split into two, where there is a pure endpoint and the other which is just better than this kitchen-sink chaos of a technical stack that we use today. I think the former is both possible and exciting, and the latter is obvious.
w
Yes, I've been trying for many years.. I just set my expectations differently nowadays
There's already plenty more sensible stacks nowadays.. the problem is getting paid to use them 🙂
d
No no, I don't support using "new representations to make code cleaner". Quite the opposite, I think much can be done by going back to the basics. Coplien's DCI paradigm helped me figure a lot of that out; but I don't think it's necessary at all to use mechanisms like DCI to do it. But I'm adamant that you can get most code, regardless of the size and complexity, into a relatively same straightforward form. This is hard to believe, but that's because you can't do it if either your data model or your execution flow model is a mess. Most practices are more one way than the other, but I know how to keep both models sane, and it's always applicable. Granted, you're not going to get simpler than the complexity of the thing you're designing; but compare how you explain it to a person, to how the code explains it. That gap can almost always be greatly reduced. Failing to do this results in a mess, regardless of language or tool, etc. Though all such things certainly are an important part of the overall puzzle to having coherent software.
w
I guess we are from very divergent planets
d
Or maybe we have very different experiences or contexts that we are operating within. If there was some example codebase, especially one that's fairly complex (not necessarily in size), I could demonstrate what I mean
w
Ok, this one is small, clean, but complex: https://bellard.org/quickjs/
d
Alrighty, when I have time (might not be soon) I'll see if I can eat my words in this code :)
I've gotta admit, based on a quick skim through, that code is pretty clean, and it's not immediately obvious that I could make it much better. Also, although it's "small" by industry standards, it's more code than I can invest my time in right now. But I suspect that the tons of functions and typedefs/enums/etc. could be replaced with (or even just organized/grouped into) fewer more high level components, which would help a ton with being able understand it all more immediately from a high level, and each component could be understood more or less in isolation. I concede that that's entirely speculative though, since I'm not taking the time to reverse engineer tens of thousands of lines of procedural code right now.
I may largely just be speaking for the kind of over-engineering that tends to occur in OOP contexts, where patterns, frameworks, IOC, AOP, etc. are applied at every possible angle. I see it a lot in C#, Java, and JavaScript. Here's an article that describes some of that: https://www.eecis.udel.edu/~decker/courses/280f07/paper/KingJava.pdf
👍 2
w
Haha, that's a hilarious article, hadn't seen that 🙂
I am very much against any forms of over engineering.. (I once wrote https://qr.ae/TWnEal :)
But when I hear you talk about DCI, modularizing, high level components etc.. I don't get it, because those word trigger exactly my over-engineering alarm bells
trying to componentize code that can't be easily componentized (which to me is most code) just leads to more complex code
d
I hear you about DCI, and that's why it took me months to really get what it was about. It sounds like a mechanistic thing, but doesn't give you any new abilities that you didn't already have before. But then I realized that it's really just about cleaning up badly factored program-flow in OOP code. It's very common in OOP to take a use case / scenario / flow and have each data entity own a slice of it. But the data boundaries are very misaligned with the overall execution boundaries, so "what the program does" becomes very convoluted. DCI is about still slicing up an execution into pieces, but keeping the relevant pieces together. Specifically, instead each class having methods from multiple use cases, those methods are associated to "roles", with are placeholders for whatever objects will participate in that execution flow / use case. But there is no need to have a flow be sliced up into methods that each most be "owned" by some object. But if that's what it means to do "OOP", then DCI is a way to put related functionality together in a way that's "still OOP". Well, I see no value in OOP if that's what OOP means; but the organization of flow AND the organization of data being two separate things that are meaningfully laid out in their own rite (rather than having one be in terms of the other) ... THAT makes a lot of sense, and doesn't need any mechanism like DCI to do it. And you'll find that such code very much matches the human mental model of what the software is and what it does. But DCI is what introduced me to thinking that way, because much OOP is very primarily "thing" oriented, or otherwise relies on complex mechanisms to "wire it up" another way. So for me, DCI (if you throw out the mechanism but look at what it's trying to do) is about just making code sane to begin with. I've had a lot of success applying those principles in my career, especially among horrible OOP code. It's my opinion that many "OO programmers" don't know (or have forgotten) how to think on simple terms. I'll post some more links, later
One thing I've found is that applying a clean top-down breakdown of the data and (separately) of the program flow, not only results in a very close match with the human mental model, but is also much simpler in an objective sense (i.e. less incidental complexity). It fits right in with Rich Hickey's "Simple Made Easy" (if you ignore the language-specific parts, e.g. why Clojure is a better language): https://www.infoq.com/presentations/Simple-Made-Easy/
w
Agreed that breaking up a task into methods that get spread out over the code for the sake of OOP is a bad thing, but I don't see DCI as the solution. All examples I see of it want to add yet more structure that add to existing OOP structures. I want to get away from OOP in its entirety
You can keep use case code together pretty easily in most languages, that allow methods or functions to be put anywhere in code so they can be grouped by use case rather than class
I personally like to keep code as linear as possible, so a particular task or algorithm can be followed locally rather than jumping all over the code base. that does not require special methodologies or language features, just a language that also allows procedural/functional style code.
d
Yeah, I think you said it very well. I think a lot of what's trying to be mechanized or fixed in OOP is just a poor replacement for plain old procedural flow/logic, and my claim is that many OOP programmers don't know how it have forgotten. When it pains me the most is when I see thousands or tens of thousands of lines of mechanism, for what could be a few hundred lines of code. My opinion is that the same way to do that with OOP is that classes represent data entities, and class methods ONLY operate on what's encapsulated by the class. Behaviors of the system itself remain more or less procedural / functional. Some languages (Java, C#) require all functions to be methods of some class. You can either have one class representing the whole system, defined essentially procedurally as a bunch of static methods, or you can break the system into discrete high level components. But really this is just the same kind of organization that can be done without classes (it's just that that's the only kind of "container" that since language offer). But there's not necessarily a gain to having a "thing" called a parser, versus saying "here's all the parsing code" like a mini library. Anyway, the classes or other containers in this sense will tens to represent what a human would think about as components of the system. For example, an ATM user might think of things called Accounts and Transactions; but something like a configuration-wire-upper or a component-manager are not real parts of a software system in any meaningful sense (unless managing configurations and components is the service that the software provides to the end user). But what amazes me is that either nobody can see it, or they don't believe it can be that simple, and so they rely on massive infrastructure to fix problems that could be handled through just fine through*basic* principles. And that's what makes me think that many software engineers don't really have a form grasp of the basics. Going to extremes to "manage" complexity that they take as a given, but which is largely artificial.
Sorry, this turned into a large rant / venting on my part. Thanks for your insight though! It's nice to know that not all software development is stuck in that kind of mess. A lot of mainstream industry software is though, and it's just very frustrating.
w
Yes, good point.. the best use of a "class" is to bundle 2 or more values that are tied by an invariant, and methods and data hiding to enforce that invariant. Any additional values, code or methods that is not strictly necessary for that purpose should really live outside the class
And yes, Java pushes programmers to entirely the wrong defaults
There are probably a lot of reasons why programmers create such unnecessary complexity. A lot of it is due to the incremental nature of software development: software can neither be written no understood all at once. It's a nice thought exercise how much simpler certain pieces of software could be if written by a theoretical god-like programmer that could conceive of all code all at once, and I think it be dramatic.. for some code like 10-100x smaller.
The other big issue people don't seem to talk about is gap between how the original creator of some abstraction sees that code (its a mapping of their mental model) and how everyone else sees that same abstraction (its a new foreign language to learn that often clashes with your mental model). Programmers somehow think everyone thinks like them, and thus WAY underestimate the cost of these new abstractions
I somehow thought that last one deserved a tweet: https://twitter.com/wvo/status/1154148443268321280
It's a tweet-sized thought 🙂
o
Thanks for this interesting discussion! 🙂
👍 2
d
Yeah, I think this slack community is great, that there can be so many discussions and thoughts just right out in the open!