Side navigation
#13103 closed feature (fixed)
Opened December 21, 2012 06:08AM UTC
Closed January 27, 2013 12:59AM UTC
Avoid Boolean traps in the API
Reported by: | ariya.hidayat@gmail.com | Owned by: | |
---|---|---|---|
Priority: | low | Milestone: | 1.9 |
Component: | effects | Version: | 1.9b1 |
Keywords: | Cc: | ||
Blocked by: | Blocking: |
Description
stop() function can lead to the following usage:
$('foobar').stop(true, false);
The weird combination of two opposite Boolean values is a classic example of "Boolean trap".
See also:
Attachments (0)
Change History (15)
Changed December 21, 2012 06:17AM UTC by comment:1
milestone: | None → 1.9 |
---|
Changed December 21, 2012 02:55PM UTC by comment:2
Totally agree on the boolean traps. Methods like .clone()
have the same issue. But migrating out to a new API and actually removing the old one would take at least two years at this point, given that we're going to slow the pace after 1.9/2.0 arrives.
So, write the new documentation for these methods. Name? Description? Is the old .stop()
method deprecated? Scheduled to be removed in 2.1? Or staying forever to allow old code to work since there is nothing functionally broken about the old API?
Changed December 21, 2012 03:22PM UTC by comment:3
If we go #2, we can slowly deprecate, but we trade for more code to maintain...
$(selector).clone({ data: Boolean, events: Boolean }); // Omitted options are assumed false, in this case "jumpToEnd" is false: $(selector).clone({ data: true }); // Both options are assumed false $(selector).clone(); // ------------------------------ $(this).stop({ clearQueue: Boolean, jumpToEnd: Boolean }); // Omitted options are assumed false, in this case "jumpToEnd" is false: $(this).stop({ clearQueue: true });
...Which is actually quite nice.
Changed December 21, 2012 03:34PM UTC by comment:4
It's worse though: http://api.jquery.com/stop/
There's an optional queue parameter at the FRONT, so it's already doing hockey. I doubt many people use that signature with custom queues (it was just added in 1.7) so perhaps we could drop that outright.
Changed December 21, 2012 04:31PM UTC by comment:5
I don't think we need to drop it... it will just become one of the options. If the type
param is an object re-assign the property values to the matching named args as needed and continue as done previously?
Changed December 28, 2012 05:04PM UTC by comment:6
component: | unfiled → effects |
---|---|
priority: | undecided → low |
status: | new → open |
version: | git → 1.9b1 |
@rwaldron: +1
Changed January 03, 2013 05:31PM UTC by comment:7
If we want to land anything for 1.9 it should happen before the next beta, which means, um, today.
I'd like to avoid going from a "boolean trap" to a "misspelled names in anonymous objects" trap.
If .stop({ clearQueue: true })
is the same as .clearQueue().stop()
(it seems to be) we should just advise people to use it. That costs us zero bytes.
For .stop({ jumpToEnd: true })
perhaps we could introduce a new API method that makes it clear what is happening, something like .finish()
that implies we're completing the animation steps immediately and not really .stop()
ping it dead in its tracks.
So this would be the mapping from old to new API:
.stop(false, false) ===> .stop() .stop(true, false) ===> .clearQueue().stop() .stop(false, true) ===> .finish() .stop(true, true) ===> .clearQueue().finish()
All that needs to be done is adding .finish()
and changing the docs to deprecate the boolean args and provide the recommended alternatives.
=
For .clone(withDataAndEvents, deepWithDataAndEvents)
there needs to be a separate discussion. Even though it accepts two booleans I suspect damn near every call just uses 0 or 1 and I'd just prefer to leave it alone.
Changed January 03, 2013 05:40PM UTC by comment:8
The .clone()
situation can be dealt with by accepting a string as the first argument.
.clone( true ) => with data and events .clone("deep") => with data and events, deep
The logic inside is quite simple:
if ( withDataAndEvents ) { // deal with data and events if ( withDataAndEvents === "deep" ) { // do it recursively } }
You can keep the old second param by changing the last condition to:
deepWithDataAndEvents || withDataAndEvents === "deep"
Pretty straight-forward. I dunno why we went for two separate arguments given how closely related they are.
Changed January 03, 2013 05:53PM UTC by comment:9
"magic string trap", "boolean trap", "misspelled property name in anonymous object trap" all sound like traps to me.
A boolean trap is simply "bad API design" and may lead to ambiguity as to purpose, but is straightforward in implementation. Magic strings make it harder to detect errors in userland, same goes for anonymous objects.
IMO, dmethvin's approach of creating new named API functions seems like the "right" "fix", in that it will error out on misspellings, and be clear in intention:
.clone(), .cloneWithData(), .cloneDeepWithData() Of course, this ruins the jQuery aesthetic of short function names, but I think it's a clarity win.
I'd say all boolean parameters should be considered for re-evaluation, but the one that comes to mind for me is outerWidth(true). The dimension functions should be called: contentBoxWidth(), paddingBoxWidth(), borderBoxWidth(), and marginBoxWidth()... no booleans.
Changed January 03, 2013 06:00PM UTC by comment:10
@jaubourg, .clone(true)
is the same as .clone(true, true)
because the deep arg is the same as the first arg unless it's explicitly specified. So the only time it comes into play is if you explicitly say .clone(false, true)
and I can't imagine a reason why anyone would need to specifically do that.
So although a .cloneWithDataAndEvents()
might make sense, to capture the current behavior we'd need something like cloneWithDataAndEventsExceptForTheRoot()
which again I think is so rare that we just act like it isn't there.
Changed January 03, 2013 07:57PM UTC by comment:11
Replying to [comment:10 dmethvin]:
@jaubourg,.clone(true)
is the same as.clone(true, true)
because the deep arg is the same as the first arg unless it's explicitly specified. So the only time it comes into play is if you explicitly say.clone(false, true)
and I can't imagine a reason why anyone would need to specifically do that.
It's even worse than what I (wrongly) assumed :/
So although a.cloneWithDataAndEvents()
might make sense, to capture the current behavior we'd need something likecloneWithDataAndEventsExceptForTheRoot()
which again I think is so rare that we just act like it isn't there.
+1
@mikesherov, seems to me you see traps everywhere... how about mispelled or badly named API functions? It's a trap!
There is no silver bullet. Booleans, strings, separate functions, it all boils down to the actual use case.
The boolean trap comes into play when you have several boolean arguments next to one another, not when you have a single boolean argument. With a single argument, the question is about ambiguity. This is the problem with the outerWidth method: true/false don't have any obvious meaning in this context.
Beside, that's why I actually love "magical" strings. They work perfectly to create $.Callbacks for instance as they should in any situation where you need to specify several "flags". I always find them to be very easy to read and make sense of given how inherently explicit they are.
Changed January 03, 2013 08:07PM UTC by comment:12
Obligatory Admiral Akbar reference: http://www.youtube.com/watch?v=piVnArp9ZE0
Changed January 07, 2013 03:20PM UTC by comment:13
My first thought on the .finish()
method is that it would complete all animations, or at least have an option to complete all animations. It seems more useful to make sure all animations on the element are done before I move on to something else.
Changed January 07, 2013 03:29PM UTC by comment:14
I was thinking we might add a .finishAll()
method for that reason but then again if you wanted to finish *only* the current step you could .clearQueue().finish()
right? So it seems the combo of those two APIs cover all the cases without need for a boolean option to .finish()
.
The interesting thing about that is that there is no easy way to do the equivalent of finishing all animation steps today and I've wanted it a couple of times. So I'd go for that. Other thoughts?
Changed January 27, 2013 12:59AM UTC by comment:15
resolution: | → fixed |
---|---|
status: | open → closed |
We landed .finish()
for 1.9.0 and the only todo is for us to talk people out of using the .stop()
signature. https://github.com/jquery/api.jquery.com/issues/236
While I completely agree with you, there is nothing we can do without...
1) breaking backward compatibility
Or
2) creating an signature overload and deprecating the Boolean args.
I'd like to leave this for 1.9/2.0 discussion