At a high-level, in your business domain, you generally write your code to hide the implementation details and to describe the intention of the method EG. `is_carbonated`
if is_carbonated(beverage):
beverage.jiggle(false)
vs if beverage.startswith("co2"):
beverage.jiggle(false)
(edit) for formattingYes, at pretty much ANY level, you should write your function names to hide the implementation details and to describe the intention of the function. In my previous post I hinted that the name of the function should "describe what the function does", which is the same as "describe the intention of the function", because if the function doesn't do what it's intended to do, that's a bug.
The reason I say I don't think you understand what you're saying is that none of what you're actually saying applies to the examples you're giving.
`is_foolike` doesn't describe the intention of the method. It gives an entirely vague and inaccurate view of what the method does. So even though we both agree that functions should describe the intention of the method, you're saying `is_foolike` is an okay name even though it doesn't do what you say it should?
`starts_with_foo` describes the intention of the method. It doesn't describe the implementation: `starts_with_foo(x)` might expand to `x.startswith('foo')` or `x[:3] == 'foo'`, but I don't care which, because the name accurately describes what it does either way.
Your example doesn't elucidate. If we're representing beverages as strings which are somehow guaranteed to begin with "co2" if the beverage is carbonated, and we've decided that the deserialization should be mixed into our "high-level, in your business domain", the program is so badly tangled that we're not going to get any truths about good programming from it.
I'm sorry that this will come across as rude, but I feel like you're repeating something you've heard but didn't quite understand.
You're right, it does come across as both rude and condescending, and you know it, so don't apologise. In my previous post I hinted that the name of the function should "describe what the function does", which is the same as "describe the intention of the function", because if the function doesn't do what it's intended to do, that's a bug.
`is_foolike` to me, implies `test_for_abstract_quality_foo`. `starts_with_foo` implies an assertion a string beginning with a particular prefix If we're representing beverages as strings which are somehow guaranteed to begin with "co2" if the beverage is carbonated, and we've decided that the deserialization should be mixed into your "high-level, in your business domain", the program is so badly written that we're not going to get any truths about good programming from it
I feel like you're intentionally misunderstanding my point. Please don't critique my entirely fictitious codebase as if it represents anything other than an abstract example. The point being that even though, in this (again) entirely fictitious example, the implementation is very simple, the intention of the method is different from it's implementationYes, 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 foo" isn't hiding implementation details, it's being opaque about what a function does.
Put another way, "what a function does" isn't its implementation. "How a function does what it does" is its implementation.
> `starts_with_foo` implies an assertion a string beginning with a particular prefix
Yes, exactly. Because that is what it is intended to do, we hope, since that's what it does. That's NOT the implementation: there are plenty of different ways to implement testing whether a string starts with 'foo', and the name `starts_with_foo` isn't coupled to any of them.
> The point being that even though, in this (again) entirely fictitious example, the intention of the method is different from it's implementation
Yes. `starts_with_foo` is also different from the implementation, while still describing the intention of the method.
Could you explain to me why you think `starts_with_foo(x)` describes the implementation `x.startswith('foo')` and not some other implementation (such as `x[:3] == 'foo'`)?
If that’s acceptable, why is an IsFooLike which only checks one condition unacceptable, even though it (imo) expresses the same intention?
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'`?
A name that is long enough to tell you all you might need to know about what the thing named does is too long to be usable at all.
A name needs to be long enough to distinguish it from the other things being named. That's what naming means. If it also gives you a hint about which thing, of the things named, it really does, that makes it perfect.
Anything beyond that adds cognitive load, making it exponentially worse with each syllable added.
In addition, if we for some reason want to change what we mean with foo-like (say to "contains("foo"), we suddenly have to change all calls to the function, in addition to the defintion.
Two questions for you:
1. If `starts_with_foo` only communicates how it's implemented, what about `starts_with_foo(x)` communicates to you that it's implemented as `x.startswith('foo')` rather than `x[:3] == 'foo'`, or some other implementation?
2. If the intention of the function isn't to test whether x starts with 'foo', how is this not a bug?
What is a bug? If the name of the function is IsFoolike, the definition of the function will be the authorative place in the code that tells us what "isFoolike" means. Then it is a semantic question if that is a bug or not. What do we mean when we say that something is Foolike?
Nothing in `starts_with_foo` describes what commands are being used--it describes what it does, not how it does it. With your logic, telling what the function does at all is too low a level.
> If the name of the function is IsFoolike, the definition of the function will be the authorative place in the code that tells us what "isFoolike" means.
Okay, so why not just name your functions, `aaaaa`, `aaaab`, `aaaac`, etc.? If the name of the function is `cdyfj`, then the definition of the function will be the authoritative place in the code that tells us what `cdyfj` means.
Aren't you worried that `isFooLike` gives us too much information about the implementation? After all, the string "foo" is in the implementation. What if you want to test for "bar" later? Then you'll have to change all the places where `isFooLike` is used, in addition to the definition!
> What is a bug?
1. You're claiming that the name should tell us what the intention of the function is.
2. You're claiming that the intention of the function isn't `starts_with_foo`.
3. The function tests to see that the string starts with 'foo'.
The only way all three things can be true is that the intention is if the function does something it's not intended to do. That's the definition of a bug.
> What do we mean when we say that something is Foolike?
The fact that nobody could possibly answer this question is exactly the problem I'm pointing out. You might as well just name the function `cdyfj`, because `isFoolike` doesn't actually give you much more information than `cdyfj` does.
No, we need to say what we are doing on the right semantic level for that part of the program. It is rarely describing exactly what we are doing, because then only function of functions will be to abbreviate common patterns of code. The main function of functions is to abstract. At this particular level, Foolike has a semantic meaning to the reader of the program who knows what a Foo is and what isn't. And in this particular version of the program we happened to implement it from the first characters of the string. But maybe later we decide that it anything that inherits from a Foo, or has the Foo interface, or just walks, but not necessarily quacks, like a Foo.
And the reason I don't call the function some nonsense is because then it is hard for the readers of the code to understand the meaning of it. And it might not help even if the they read the definition of the function, because the definition isn't the meaning. It doesn't say anything about why it is interesting to know if the string starts with 'Foo' or not (which is, as I have mentioned, the problem with the name you suggested).
>> What do we mean when we say that something is Foolike?
>The fact that nobody could possibly answer this question is exactly the problem I'm pointing out.
No, we already know what foolike means. It is part of the domain that we are working with. The people who wrote the code and the people who read the code are assumed to have enough domain knowledge to know what a Foo is, and they understand me when I say things "we better use a Foolike here, otherwise we'll get a Bar problem".
Let's use a bit less abstract example. Say I implement the function NextInvoiceNumber(). My first implementation might be something like {return ++n} (because I'm making a proof of concept and I'm in a hurry). You seem to argue that a better name of the function would be something like AddOneToNAndReturnN(). But then the reader of the code won't understand what the purpose of the function is, because nothing says anything what n is good for and why we are interested in getting the next n. And the moment I start to add different number series for different articles and some of them increment with 10 and some with 100, the name AddOneToNAndReturnN() will be wrong. That will not be a problem for NextInvoiceNumber(), because that is perfectly normal for anyone who has done any work with invoicing. Invoicenumbers are part of the domain that the developer must know about.
It sure doesn't. Maybe readers are much smarter than me, but I don't know that it means to be foolike until I read the function definition. Apparently something is foolike if it is a string starts with 'foo', but I certainly wouldn't be able to guess that from the name. And I still don't know what a Foo is or isn't.
> But maybe later we decide that it anything that inherits from a Foo, or has the Foo interface, or just walks, but not necessarily quacks, like a Foo.
Okay, so you're naming this based on what it might do in the future rather than what it does? How are you predicting the future here? And when it does the future thing, will you change the name so that it is talking about the future again, since you have such an objection to names referring to what a function presently does?
> And the reason I don't call the function some nonsense is because then it is hard for the readers of the code to understand the meaning of it.
The concept of being like a food IS some random nonsense: it's hard for readers of the code to understand the meaning of it.
> Let's use a bit less abstract example. Say I implement the function NextInvoiceNumber(). My first implementation might be something like {return ++n} (because I'm making a proof of concept and I'm in a hurry). You seem to argue that a better name of the function would be something like AddOneToNAndReturnN(). But then the reader of the code won't understand what the purpose of the function is, because nothing says anything what n is good for and why we are interested in getting the next n. And the moment I start to add different number series for different articles and some of them increment with 10 and some with 100, the name AddOneToNAndReturnN() will be wrong. That will not be a problem for NextInvoiceNumber(), because that is perfectly normal for anyone who has done any work with invoicing. Invoicenumbers are part of the domain that the developer must know about.
Okay, I'm guessing you mean something more like:
int getNextInvoiceNumber() {
return ++this.invoiceNumberCounter;
}
And that's perfectly reasonable, because getNextInvoiceNumber describes what the function does.A name like addOneToInvoiceNumberAndReturnInvoiceNumber would be bad, not because it's inaccurate, but because it doesn't tell you what the function does in context.
We don't have any similar context to concern ourselves with for 'starts_with_foo'.
It seems like going back to the beginning of the discussion, you guys assigned some meaning in your heads to the adjective "foolike" that you didn't choose to share with me and decided based on this meaning in your head that I was wrong. Perhaps the name `is_foolike` is a better name within the imaginary context you've created, but I was talking about the code that was linked on this thread, not the code you've imagined and didn't tell me about.
If I was doing a code review and came to this:
I would comment, but my comment would be, "Could you name this function `starts_with_foo`?"This addresses both concerns mentioned in the article:
1. During review, when a reviewer is asked to verify that code is sensible before it can be merged into the main project. That reviewer probably has about a tenth as much time to spend as the original author does on that code. But if the reviewer comes to the code
They aren't likely to be misled in any way by the indirection. They can just keep reading, without having to look into the implementation of `starts_with_foo`, because either that name is accurate and they know what it does, or they'll discover that the name isn't accurate when they review the code.2. While debugging future issues. This code will eventually be involved in a bug and some completely different developer will have to glance at this code to figure out what’s going on. They’ll have to understand some small section this code within a few minutes to determine what is relevant. They won’t be able to invest the time to understand the full thought process behind it, and a web of function definitions can slow down this process considerably. But again when they read the code, a good name means they can understand what the code does without having to read the implementation.
I do think there's an argument to be made that indirection is a problem here, but it's a small problem compared to the enormous problem that `is_foolike` is a really bad name for that function.
General rules for when to NOT pull something out into a function:
1. If it the function call wouldn't be clearer than the code (even a better name like `starts_with_foo(x)` loses some information contained in `x.startswith('foo')`, and the latter is readable enough that there's no real upside to the former. If the latter were even two lines long, it would become a lot more worth it.
2. If there's no repetition. Two similar pieces of code aren't enough: you don't understand from two use cases what pattern you're abstracting, so the result is just going to be a leaky abstraction. Three repeated pieces of code seems to be the sweet spot: now you have enough examples to know what's actually repeated, what should be arguments to the function, etc.[1]
[1] https://blog.codinghorror.com/rule-of-three/