Live data from Hacker News

Avoid Indirection in Code

matthewrocklin.com

151–160 of 220 posts

Re: Avoid Indirection in Code

#151
Yeah avoid indirection. Instead of

> x.start_with("foo")

do

> x.length >= 3 && x[0] == "f" && x[1] == "o" && x[2] == "o"

Now the reviewer doesn't have to jump to the definition of "starts_with." Isn't that way more readable?

Re: Avoid Indirection in Code

#152
post #95

Earlier quoted context omitted.

In many cases speaking of the foolike nature of a value is not dissimilar to speaking to the primeness of an integral: Is a value prime? And now here's is an algorithm for determining whether this value is prime. The motivation for many splitting out a prime-testing function is that primality testing is hard to do efficiently, not that there are many kinds of primes or many kinds of integrals. Indeed, in some languag…

> Nothing is being abstracted here either- we're still only talking about giving this algorithm a name I would say giving something a name that summarizes it so you can refer to it by the name as shorthand (and in code, "referring to" means "using")... is in fact exactly what "abstraction" is, it's possibly almost a good working definition of "abstraction" in fact.

When a Computer Scientist or a Mathematician says "abstraction" they do not mean the same thing that a layperson means. This is what I am referring to: https://en.wikipedia.org/wiki/Abstraction_(mathematics)

Abstraction in mathematics is the process of extracting the underlying essence of a mathematical concept, removing any dependence on real world objects with which it might originally have been connected, and generalizing it so that it has wider applications or matching among other abstract descriptions of equivalent phenomena

I absolutely do not mean "refer to it by the name as shorthand", and I believe such a definition to be incredibly unhelpful: Some languages do not have variables (or do not need them), and yet they still have very powerful expression-building capabilities useful in abstractions, however I urge you to distinguish between "giving something a name" and "abstracting" something for a much more important reason:

We can waste a lot of time looking for a good name, or we could waste a lot of time looking for a good abstraction. The latter will always pay a higher return than the former, so I do not think we should make it easy for young impressionable minds to confuse these two things.

Re: Avoid Indirection in Code

#153
post #56

The point made by the article (avoid using due to problems for debugging and code review etc.) does not hold much water. 'Indirection' can be invaluable while separating 'how you got it' from 'what to do with it' (it=some data), for example. 'what you do with it' can remain unaffected by changing 'how you got it'. This will not mess with code review practices but make understanding the pieces easier. It will also mak…

Thanks for sharing Zed Shaw's wisdom. I will chew on it.

"Abstraction is used to reduce complexity."

My taxonomy: Abstractions hide complexity (bad), mental models simplify (good).

I'll consider how Shaw's worldview relates to my own.

"Indirection is used to reduce coupling or dependence."

My taxonomy: Indirection defers a design choice. A la Design Patterns.

One of my heuristics, when weighing alternatives, is to pick the design with the shallower stack depth. I'm just not smart enough to keep track of many moving parts.

FWIW: I see the 'isPrime' function more as macro (shorthand) done for legibility than a potent future cutpoint (eg Strategy).

Re: Avoid Indirection in Code

#154
post #40

It's not indirection , it's bad abstraction that's the real issue here. Consider the example used in the article: if x.startswith("foo"): do_something_with(x) if is_foolike(x): do_something_with(x) The problem with both of these variations is that the "if-statement" doesn't have any meaning behind it. There's no gain in the indirection presented here. Whereas the following code has meaning: if checkHasPermissions(x):…

When the author used generic "foo" examples, it's hard to tell if there's meaning behind it or not. In a real example, there might very well be meaning behind it, perhaps his "foo" examples in his actual life are more like your "checkHasPermissions" example. But I think you're right that "abstraction" is the important concept here, which "indirection" is either a synonym for or one part of, depending on what you mean…

> In actual experience, I think most of us end up erring on the side of too much/too complicated abstraction ("over-engineering"), so when it doubt, it makes sense to try erring on the side of less abstraction, including duplicating code at times (do not let "DRY" be your only guide).

Yeah, this. I've noticed a lot of younger engineers(myself included when I was starting out) have a tendency to try to avoid duplication at all costs, even when it's unclear whether the two pieces of code will evolve in the same way over time.

So you wind up with a single class but then a year later you have to rip the single class out and replace it with the repetition of the code, and then make a change that makes the two cases no longer repetitious. It would have probably been better to repeat the case.

My heuristic for deciding whether to replace duplication with abstraction is whether I've copy-pasted the same code at least three times, and/or it is code where eliminating the abstraction is trivial. So if I can replace the code with a simple function I'm more apt to do it than if I would have to create a class or composition of classes to do the same thing that the duplication does. It's way more maintainable at that point to just leave the copy/paste in and deduplicate when there's actual need to create a new tool in the code base.

Re: Avoid Indirection in Code

#155
>However, there is also a cost to this behavior. When a new reader encounters this code, they need to jump between many function definitions in many files. This non-linear reading process requires more mental focus than reading linear code.

I think this is the problem right here. As a new reader you should first trust the function name does what it says it does and continue linearly. Later on, if you need to go back and look at the details or where the function name misleads or does more than it says.

Re: Avoid Indirection in Code

#156
post #152

Earlier quoted context omitted.

> Nothing is being abstracted here either- we're still only talking about giving this algorithm a name I would say giving something a name that summarizes it so you can refer to it by the name as shorthand (and in code, "referring to" means "using")... is in fact exactly what "abstraction" is, it's possibly almost a good working definition of "abstraction" in fact.

When a Computer Scientist or a Mathematician says "abstraction" they do not mean the same thing that a layperson means. This is what I am referring to: https://en.wikipedia.org/wiki/Abstraction_(mathematics) Abstraction in mathematics is the process of extracting the underlying essence of a mathematical concept, removing any dependence on real world objects with which it might originally have been connected, and gene…

It's not about the name, it's about the boundaries of the concept, but how do humans establish a concept and it's boundaries without giving it some kind of name to refer to?

But I am may not be familiar with the kinds of languages you speak of, where there are no variables (and no functions/procedures? No names at all?) but "still have very powerful expression-building capabilities." I'm not totally sure what you're talking about, i admit.

In the OP, describing a pretty strictly procedural/functional environment, I think taking logic and putting it in a procedure with a name is the fundamental tool of abstraction offered by such an environment.

Re: Avoid Indirection in Code

#157
post #58

Earlier quoted context omitted.

Yup, seems like the author had trouble following badly written abstraction and/or indirection and came up with the idea that they are bad. Abstractions are invaluable if you are dealing with a large codebase. Even the original author can’t keep everything in their head. Indirections are invaluable if you want to be able to modify code in the future without making a gigantic mess — and potentially breaking compatibili…

Well, good abstractions/indirections are hard . The wrong abstraction can be worse than no abstraction at all. And it can be non-obvious if you've got the "wrong" abstraction until much later when it's been in use for a while and had to be maintained -- and sometimes even then, we aren't good at recognizing that our pain is coming from the use of the wrong abstractions. We often are, without necessarily realizing it,…

See my top level comment on this post.

Re: Avoid Indirection in Code

#158
There are a lot of examples of heavyweight indirection in Hardware Abstraction Layers (HAL) on microcontroller vendor code - ST is one of the worst offenders! In their sensor code library I counted 9 layers deep of functions to simply send a command to a magnetometer over I2C.

I find this code hard to work with since without actually debugging the code there is no easy way to just click on a function and see what is called - a lot of functions reference a data structure of I2C function pointers...

Re: Avoid Indirection in Code

#159

Earlier quoted context omitted.

> `is_foolike` to me, implies `test_for_abstract_quality_foo`. Yes, which is why when `is_foolike` tests that a string starts with 'foo', that's rather unexpected. If `is_foolike` actually describes the intention of the function, then the function testing for the string beginning with 'foo' is a bug, because it doesn't do what it's intended to do. Referring to "the quality of starting with 'foo'" as "abstract quality…

Ok, I’m curious. Let’s say that I want to fall into that if statement if my product is named something like “foo”, costs under $100, isn’t discontinued, and has sold over 100 units in the last month. Fine, you can say now I have overcomplicated the domain and I should go back to requirements, but these complicated things do happen. Surely it’s acceptable to name my function “isFooLike” rather than “startsWithFooAndCo…

> Ok, I’m curious. Let’s say that I want to fall into that if statement if my product is named something like “foo”, costs under $100, isn’t discontinued, and has sold over 100 units in the last month. Fine, you can say now I have overcomplicated the domain and I should go back to requirements, but these complicated things do happen. Surely it’s acceptable to name my function “isFooLike” rather than “startsWithFooAndCostsLessThan...” etc, especially if I suspect management may change some of those figures later?

The idea with naming is to capture as much of the meaning of the function as possible, and 'isFooLike' doesn't really capture any meaning (what does it mean to be like a foo?). In business, there are typically names for the topics, like `is_profitable_transaction` or `meets_sales_targets`, which may indeed encompass some very complex logic, but are a coherent idea, so naming is usually a bit easier than this. But you're right, sometimes the requirements are bad and you don't have a chance to go back to requirements before a deadline. In those cases, I don't think the naming matters much because no name you come up with is going to represent the concept. So I guess `is_foolike` might be the best you could come up with, but it's certainly not good code. I'd also be less likely to pull out a function in the first place because it's a premature abstraction: if the abstraction isn't coherent enough to be named well, we probably don't understand it well enough yet to abstract it out.

> If that’s acceptable, why is an IsFooLike which only checks one condition unacceptable, even though it (imo) expresses the same intention?

It's not the same intention.

In the more complex case, you have a bad name because the name doesn't describe the (overly complex) intention of the code, but there isn't a better one.

In the second case, you have something that does a clear thing, so you should name it what it does. For a more complex case, you're not going to be able to capture every nitpicky detail of what the function does in the name, so you have to describe it in a broader concept. But with a short simple function like this, there really isn't an excuse for using a vague description.

I think you're still ignoring my explanation that what a function does and intends to do is different from how it does it. So I'm going to again insist that the two of you answer these questions to prove they actually understand my points before disagreeing with me:

1. I'm claiming that in correctly-working code, the intention of the code IS what it does. Is there a case where code would do something other than it's intended to do, and this isn't a bug or at least misleading code?

2. I'm claiming that what a function does is not the same as its implementation. Why are you claiming `starts_with_foo(x)` coupled to the implementation `x.startswith('foo')` and not `x[:3] == 'foo'`?

Re: Avoid Indirection in Code

#160
post #121

Earlier quoted context omitted.

At this point you might also consider a `try: treat(x); catch unauthorized: fallbackly_treat(x)` pattern.

Disagree. Someone attempting to do something while unauthorized is oftentimes not an exceptional state and regular business logic depending on how access is given to different endpoints. Do not use exceptions, which are "heavy" (capturing call-stack etcetera), for something that commonly occurs.

Using an exception for authorization failures avoids TOCTOU security holes. Also, in many cases, there are intermediate layers of the call stack that don't have any useful contribution to failure handling; the best they can do is forward the failure return value up the stack. And if one part of an operation fails because of a lack of permissions, it is usually dangerous to continue processing without handling the failure, which is the default behavior that failure return values have in most languages.

By contrast, the performance cost of capturing stack traces when there's an authorization error is insignificant. How many authorization errors do you have per second in normal operation? For many systems, the answer is down in the millibecquerels to microbecquerels.

Under these circumstances, the balance leans quite heavily to the side of using exceptions to report authorization failures.

Post reply on HN