If you run this JavaScript function on the 31st of a month, the result will be a month off. The best part is that this is the intended behvaior. JavaScript is a cursed language.
function getMonthName(monthNumber) {
const date = new Date();
date.setMonth(monthNumber - 1);
return date.toLocaleString([], { month: 'long' });
}
I agree with you that errors are useful feedback for coders who don't know the ins and outs of an API. And every programmer is in that group at some point. But the difficulty in identifying this particular bug doesn't stem from the API decisions.
Whether Dates throw an error, or work with what they're given, has no bearing on the subtlety of this bug. Either way, tests that don't replace Date will fail to identify it most of the time, and tests that do, based on its use within the function, would be called wrong-headed by many.
Either way, the bug only shows up at the end of months longer than the target month, and that infrequency has nothing to do with the peculiar design choices of the Date API. It stems exclusively from the evaluation of Date() called with no arguments returning different values at different times—behaviour you have not objected to, and which I'd expect to be considered entirely appropriate, in fact its very point—combined with an attempt to use that value, whatever it may be, without due consideration.
Since the month is the only part of interest, there's no reason to allow the other parts to vary at all. Fixing them, as I suggested at the beginning of all this, is the simplest approach, but setting them first, as has also been suggested, would work too.
You can once again complain about JS design decisions and I'll agree about many of them, but, as much as you might like it to be, and as annoying as so many of us think they often are, here it is beside the point. The perniciousness of this particular bug stems from unnecessarily calling a function with inconsistent output and then improperly processing that, instead of using a function call with always-predictable output.
I've tried to point that out in all the ways I can think of, so if it's still not getting through, I give up. And if your acknowledgement was too subtle for my sleepy brain, and I've ended up overexplaining, then I'm sorry.
The difference is that hrowing an error makes it much easier to find the bug early, while doing the wrong thing silently makes it much harder to do so. If an error is thrown by the API then the first time wrong input is supply the application will fail and you'll know you have a problem. If the API silently does the wrong thing, then the application will keep doing the wrong thing until somebody notices and that tends to be far more costly.
Finally, I'd like to note that this isn't a hypothetical debate. This is how APIs work in sane languages such as Java:
Yes, and I've said that I agree with that in general. I know that this isn't hypothetical; that's exactly why I keep saying that throwing an error doesn't help you find this bug early at all.
Even the silent weirdness can be caught by the most basic of tests checking output against input, but only if your function works the same way on every invocation.
Whether making a giant fuss (as you'd prefer) or making the best of it (as it actually does), the setMonth method always works the same way. My code always works the same way. The setDate suggestion makes the code always work the same way.
Code that always works the same way is easy to test.
If the day of the month is constant and incompatible with setMonth, whether there's a thrown error or just an unwanted return value, a simple test will reveal that on every test run. If the day of the month is constant and always compatible with setMonth, the test will pass appropriately on every test run.
The bug in the code you originally presented comes from working differently over time. That's why, most days, tests won't identify the problem, even with a fussy, noisy API. Most testing days, the date will just happen to be compatible, and even the fussiest, noisiest API will carry on without any mention of the problem.
The reason the original code works differently over time has nothing to do with the silent, unexpected behaviour of setMonth. It's entirely down to calling Date() without arguments, the entire point of which is to give different values over time. That call effectively introduces state that is not controlled by the function. And not bringing it under control is the real source of the bug.
Yes, absolutely, JavaScript sucks. Make F# the only supported web scripting language! But JavaScript's suckiness is not the cause of this particular bug. JavaScript's suckiness is not the reason this bug is hard to catch. The real problem lies in code that functions differently over time when it should (and could easily) be consistent. That's what actually makes it hard to test.
Plenty of other languages and API design choices still allow code that functions that work differently over time, which is why, as justifiable as the complaints are in general, those factors are irrelevant for this particular bug. Write code that always works the same way and the problem goes away. That's the real core of the issue.
Obviously, that's easier said than done, and it's irritating that neither loud errors nor most testing will help you in this regard, but that's the way it is.
Whether making a giant fuss (as you’d prefer) or making the best of it (as it actually does), the setMonth method always works the same way. My code always works the same way. The setDate suggestion makes the code always work the same way.
You're missing my point entirely here. Current behavior works in a SURPRISING way and SILENTLY produces an output that's most likely to be unintended. Let me give you a concrete example of the problem here.
Let's say you have a calendar app that shows the current day by default, and then there are buttons to go to next or previous month. To get the current day you'd have to call Date(), and then you'd have next and previous month functions that would work off the date you got. In Js world these functions will mostly work, but once in a while give users a wrong month silently.
The bug in the code you originally presented comes from working differently over time. That’s why, most days, tests won’t identify the problem, even with a fussy, noisy API. Most testing days, the date will just happen to be compatible, and even the fussiest, noisiest API will carry on without any mention of the problem.
That's not the problem at all, and your whole line of argument here is frankly bizarre. There are plenty of use cases where you need to have functions that do something based on a current date. That means needing to get the date from the system without knowing what that date is up front by calling Date() without set arguments. This isn't some anti pattern that you keep trying to make it out to be. There is absolutely nothing wrong with getting the current date.
I'm not missing your points, even as you change them. I've agreed that JS sucks. I've agreed that errors can be more helpful. I'm not trying to argue with you about that. What I have said, from the beginning, is that in the code you originally presented a behavioural change for setMonth will not help you find the problem any faster. Test failures for the wrong output occur just as often as test failures for errors, on exactly the same few days each year. The API change gives no advantage for the specific function this discussion started with in this regard. However, an approach that avoids inconsistency will, because in this particular instance, that is the real source of the problem. That is all.
In that context—the one you started with—it does not matter that there is often good reason to call Date()without arguments. The getMonthName function presented, effectively an array lookup, should produce the same output for any given input every time. It has no reason to engage in any behaviour that varies from day to day.
There is absolutely nothing wrong with getting the current date.
Bluntly, the code you presented fails precisely because it gets the current date where it should create a more specific one, and then fails to deal with that variation appropriately. You can keep distracting yourself with language design decisions, but that won't help you avoid this particular type of problem in the future because that's not where it is.
Getting the current date is often fine. In this specific instance, it is not. That is why the function doesn't work. If you are missing that point, as much as I appreciate your enthusiasm in continuing the conversation, I will take the L (and the code that actually works) and move on.
What I have said, from the beginning, is that in the code you originally presented a behavioural change for setMonth will not help you find the problem any faster.
Yes, it would help find the problem faster because the first time invalid date is passed in the program will crash. The current behavior means the program will keep running and the only time it will become apparent that there is an error is when somebody notices that the month is wrong. You keep saying you're not missing my points, but here we are.
In that context—the one you started with—it does not matter that there is often good reason to call Date() without arguments. The getMonthName function presented, effectively an array lookup, should produce the same output for any given input every time. It has no reason to engage in any behaviour that varies from day to day.
Again, the point that was actually being made is that this whole scenario can be avoided by rejecting invalid inputs for the date of the month.
Bluntly, the code you presented fails precisely because it gets the current date where it should create a more specific one, and then fails to deal with that variation appropriately.
Bluntly, I've already explained to you that the code presented is not the problem and is a valid use case.
Getting the current date is often fine. In this specific instance, it is not.
This specific instance is a legitimate use case for getting the current date because the intent of the code is to get the previous month RELATIVE to the current date. The code you provide simply hacks around this problem by hard coding the date.
the intent of the code is to get the previous month RELATIVE to the current date.
But that isn't what it does. From the original post:
function getMonthName(monthNumber) {
const date = new Date();
date.setMonth(monthNumber - 1);
return date.toLocaleString([], { month: 'long' });
}
That is a function which is meant to take a number (presumably 1 to 12) and return a localized name for it. This is essentially an array lookup and should return the same output for a given input (and locale) every time it is called. If the intent is to return a value relative to the current date, it is even more wrong, since it should gather the month from the current date, not the function paramenter. This claim of intent, not present in the original post, is an example of you changing your story over time.
Yes, it would help find the problem faster because the first time invalid date is passed in the program will crash.
No, it wouldn't. As I have said before, testing for unexpected return values is just as effective as testing for errors, that is, not very with the function originally presented under sensible assumptions. If the function actually did look like the intent you claim, the tests would be different, necessarily replacing Date for consistent runs, but would be equally likely to catch the problem whether failing on value or error. And if you are eschewing testing and relying on runtime crashes, you have bigger problems.
Given that I have agreed and commiserated, and neither of us can change JavaScript, there is nothing to be gained from pursuing this complaint. In contrast, what I have tried to say, if followed, would give you an approach that leads to more reliable code, even in the face of undesirable APIs.
I had thought that worth pursuing, and had thought you worth investing my considerable time. Alas, I can only lead you to the water...
But that isn’t what it does. From the original post:
The problem would be the same if you were just doing an offset from the current month. You're now nitpicking the example while ignoring the point being made. Perhaps this version will help clarify the problem for you better:
function getLastMonthName() {
const date = new Date();
date.setMonth(date.getMonth() - 1);
return date.toLocaleString([], { month: 'long' });
}
No, it wouldn’t. As I have said before, testing for unexpected return values is just as effective as testing for errors, that is, not very with the function originally presented under sensible assumptions.
I'll repeat this again, the failure case is not obvious and you can easily miss the test for it. Throwing an error makes it much easier to identify that there is a problem, and that's why APIs in sane languages such as Java behave this way.
This claim of intent, not present in the original post, is an example of you changing your story over time.
Nobody is changing the story over time, you're just incapable of acknowledging being wrong.
Given that I have agreed and commiserated, and neither of us can change JavaScript, there is nothing to be gained from pursuing this complaint.
Which is precisely why I posted this on Programmer Humor. Js is a garbage language, and it's obviously beyond fixing, but I can laugh at it.