4 ms·
this entire blog post reduces thusly: "hiding complexity with abstraction and encapsulation is not simplification, but further increases cognitive load as the a
by aaronblohowiak 17y ago
this entire blog post reduces thusly: "hiding complexity with abstraction and encapsulation is not simplification, but further increases cognitive load as the abstractions must be undone to fully comprehend the function of the code." It is an interesting point, sure. I think the example as given makes his point fairly well.
The question becomes: when does hiding complexity improve your ability to reason about the code?
I agree with the author that this is ultimately a personal preference. One guideline that I appreciate is that your code should operate at a consistent level of abstraction. For instance, in the routine that defines the business logic, the details of the protocol used for persistence should be "somewhere else." This complicates tracing the execution of the program, but it makes understanding the intent of the business logic easier as there are less "implementation details" to ignore.
- DougBTX 17y agoThe question becomes: when does hiding complexity improve your ability to reason about the code? When you trust the abstraction. For example, on the whole we trust the filesystem abstraction, we rarley try to inspect the phisical location of the bits on the disk - the filename is sufficient, we can treat the fielsystem as a black box. On the other hand, if you don't trust the code, you're going to want to read every line, and that's easier to do if all the code is in one method. An abstraction is a liability if you can't trust it. Re testing: it's easier to trust a well tested function, so splitting up code to make it easier to test, and then testing it, will allow you to ignore it's implementation. Suddenly it isn't logic spread across six classes, but one class using some utility methods.
- chousuke 17y agoWhat kind of abstractions are the most trustworthy? In my experience, the ultimate in abstraction is anything that works solely with immutable data. This includes, obviously, pure functions, but also objects which have no mutable state. Such things are trivial to test and perfectly composable. It's easier to gain trust in them as you rarely need to know exactly what they do, only whether they appear to work or not. Obviously immutability alone is not enough for many abstractions, but I would still like to see it used more. The current object-oriented models seem to encourage encapsulating state, rather than encapsulating data, which seems to lead programmers to use mutable things even when they are unnecessary. Unless the abstraction requires it (eg. IO), I think any state held within an object is, in a way, leaky abstraction. As long as there is state within an object, it is not obvious whether you can, at any one time: 1) call its methods 2) hold a reference to it 3) delete it, or your reference to it (Mostly applies to non-GC languages) 4) Pass it as a parameter to something All these worries disappear with immutable values.
- wisty 17y agoPure functions have one problem - they don't have state, and sometimes you need state. What's needed is pure functions for logic, and dumb objects holding data, and function pointers.
- elblanco 17y ago> When you trust the abstraction I agree. I would also say that in cases where the abstraction doesn't make sense you have to know what's going on inside the box in order to trust it. In your example, the file system as an abstraction makes sense because once you understand that it's a tree, you are pretty much good to go. I remember working on some projects early in my career with a very senior developer who had a habit of making the strangest abstractions possible on a problem. For example, need to use a class to define a triangle? His constructor might take 7 arguments! The center of the triangle on the screen, and the distance from the center as well as the degrees of rotation from the center (with the bottom of the screen being 0 degrees). Oh, and actually, it didn't take 7 arguments, it took 4, but three were some special object type containing a tuple of radians plus a mishmash of other parameters and a coordinate on screen. Because he wanted to, you know, reuse that code again. So in order to build a triangle, I had to define a center point on screen (with 0,0 in the middle of course because that's how his code worked) and then calculate the coordinates of the three triangle points from the distance I wanted, then convert degrees into radians or some such....bah! I don't remember all of the details, this was a long time ago, but it was horribly obfuscated and made no intuitive sense. He explained that it was all to avoid some singular edge case that he had encountered once, and he thought it was a good trade-off because all of the new edge-cases it introduced were manageable. When I received this code (without documentation or useful comments), he was on vacation for two weeks. Naively, I assumed it was an easily understandable abstraction in that I could simply supply 3 coordinates in some order to the library and get a triangle. I spent a couple days trying to figure out the order I was supposed to issue the coordinates to get it to draw before giving up and just reading the code to figure it out. Worse yet, the internals were abstracted all to hell in a similar obfuscated fashion and I literally got nowhere in trying to figure it out. I actually just waited for him to get back to walk me through the code, peppering him with question like "do we really need to define the z coordinate as 0 all the time since the display is always 2d?" before proceeding on that work. Once I understood it, I just wrote some wrapper code to translate three normal 2d coordinates into his craziness to simplify my life. I ran into this kind of thing with his abstractions all the time. From the most insane string class you have ever seen to a home rolled virtual memory library that pickled objects onto disk, but all of your objects had to be built around a base class that was full of useless virtual functions that you had to implement.
- Qz 17y agoIs the goal to hide complexity? Any non-trivial program is going to be complex no matter how you refactor it.
- prodigal_erik 17y agoFor me the goal is to divide the complexity at natural boundaries so I can just focus on a portion of it. Sometimes I care about the abstract contents of an object I'm transmitting. Sometimes I care about how objects in general are serialized into a stream. Sometimes I care about how a stream is spread across TCP packets. But I hardly ever care about all these layers simultaneously, and it'd be harder to make any changes I need if I had to understand and correctly maintain the invariants of all of them.
- Chris_Newton 17y ago> The question becomes: when does hiding complexity improve your ability to reason about the code? How about this: "when the degree of complexity hidden is greater than the extra complexity introduced by the abstraction mechanism"? Edit: Here's an example to show what I mean. Suppose we have a 2D array representing a matrix of values and we want to print it. We could write an algorithm like this: function print_matrix = for each row i for each column j print_element i j Or we could write something like this: function print_matrix = for each row i print_row i function print_row i = for each column j print_element i j In the second version, we might have shorter functions and less nesting, but we haven't significantly lowered the level of abstraction between one function and the other, so there is little real benefit. On the other hand, we have reduced the cohesion because now the reader must follow the logic through two functions instead of one, and this is bad. There is too much extra complexity created by introducing the second function and not enough hidden complexity because the levels of abstraction aren't much different, so breaking out the inner loop is a bad trade-off that makes it harder to reason about the code.
- deleted 17y ago[deleted]