-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Required options/named parameters #930
Comments
Also from the code review: /**
* @constructor
* @extends {Object}
*
* @param {Object} options - Has the following fields:
* @param {Array.<EntryType>} options.horizontal
* @param {Array.<EntryType>} options.vertical
* @param {Array.<Array.<EntryType>>} options.products
* @param {EntryType} options.total
* @param {EntryType} options.horizontalTotal
* @param {EntryType} options.verticalTotal
* @param {AreaChallengeType} options.type
*/
// REVIEW: This is unconventional and potentially incorrect way to document options.
// REVIEW: @zepumph said he normally solves this problem with a jsdoc typedef
// REVIEW*: A typedef that is used once always feels like something that is wrong and should be inlined. Can we
// REVIEW*: discuss and investigate options for documenting this?
// REVIEW: This seems related to https://github.com/phetsims/tasks/issues/930
// REVIEW: I'll add a note about it there
function AreaChallengeDescription( options ) { |
I would be fine with required params in options as long as the documentation was solid. I worry about inconsistencies about how we document objects playing into the effectiveness of communicating that a parameter is required for the type. Let's say we settle on the above documentation as the recommended approach for documenting options (not sure it is), then I would like to see something like: |
IMO, options should be optional. I've coded all my sims that way and that is my expectation when looking at other developer's sims, and it will increase the difficulty of interpreting other developers' code if we don't all follow this convention. If there are parameters that are required, I think they should either be separate individual arguments if there are only a few, or if grouping them together is easier, there could be a "parameters" or "config" object. |
To provide examples of the preceding comment: new Person1({
name: 'larry', // name is required
age: 45, // Age is required, so it appears in the a "config" parameter
},{
hairColor: 'blue' //hair color is optional
});
function Person1(config,options){
assert && assert(config.name,'name must be supplied');
assert && assert(config.name,'name must be supplied');
this.name =config.name;
this.age = config.age;
options = _.extend({color:'black'},options);
this.hairColor = options.hairColor;
}
new Person2({
name: 'larry',
age: 45,
hairColor: 'blue'
});
function Person2(options){
assert && assert(options.name,'name must be supplied');
assert && assert(options.name,'name must be supplied');
this.name =config.name;
this.age = config.age;
options = _.extend({color:'black'},options);
this.hairColor = options.hairColor;
} |
I'm totally fine with |
My 2 cents:
No.
Yes
No. If you have something that complex, you probably need a new type, or at least a data structure type. (
No. Keep it simple. Named parameters are required. Options are optional, and default values are provided for anything that is optional. If your argument list gets too long, consider a data structure type, or live with it. |
The original example is AreaChallengeDescription, where descriptions are created like: AreaChallengeDescription.LEVEL_1_NUMBERS_1 = new AreaChallengeDescription( {
horizontal: [ GIVEN, GIVEN ],
vertical: [ GIVEN ],
products: [
[ GIVEN, GIVEN ]
],
total: EDITABLE,
horizontalTotal: GIVEN,
verticalTotal: GIVEN,
type: AreaChallengeType.NUMBERS
} ); and you are saying we would prefer: AreaChallengeDescription.LEVEL_1_NUMBERS_1 = new AreaChallengeDescription(
[ GIVEN, GIVEN ],
[ GIVEN ],
[ [ GIVEN, GIVEN ] ],
EDITABLE,
GIVEN,
GIVEN,
AreaChallengeType.NUMBERS
); which is WAY harder to read. And if it's that complex, I don't know what you mean by a new type, because AreaChallengeDescription is meant to be an object with those properties. It IS the data structure type. So, do people actually prefer the second one without names? People want to have to look up what the 6th parameter is? If you are recommending something different, can you please show exactly how the client usage would look here? |
In my opinion, inventing another "named, required parameters" object that could be supplied in parallel to |
So... what if we take everything named |
Orientation.js is also a place that would also use this pattern, with all required fields, e.g.: {
coordinate: 'x',
centerCoordinate: 'centerX',
modelViewName: 'modelToViewX',
minSide: 'left',
maxSide: 'right',
rectCoordinate: 'rectX',
rectSize: 'rectWidth',
layoutBoxOrientation: 'horizontal'
} |
For AreaChallengeDescription I was imagining something like this: /**
* @param {DescriptionConfig} descriptionConfig - see required properties in DescriptionConfig
* @param {options} options
*/
function AreaChallengeDescription( descriptionConfig, options ) {
options = _.extend( {
shufflable: true,
unique: true
}, options );
// assign rest from descriptionConfig I would prefer this to an object that can have both required and optional fields, even if it is renamed to something other than "options". |
Then this passes the burden onto |
@jonathanolson asked:
No. Using an object literal data structure (sin the current implementation of AreaChallengeDescription) is fine. |
I think @jessegreenberg has the right idea. Keep optional things in |
I like the idea of using |
That also sounds inconvenient if we need to have a separate nested config object alongside a nested options object (for components that things through). Maybe that can be avoided? |
Brainstorming, what about having a reserved key in the options object called required, to nest required args under. Weird usage though:
|
I'm fine with mixing required and optional fields in |
Current proposal:
Uncertain:
|
Leaving dev meeting tag for more discussion. @jessegreenberg, can you provide feedback in advance of the meeting? |
I think it could be confusing to have optional fields in
We won't have to worry about this if we put optional fields in options. I could learn to look through |
Examples for config only ( function One( config ) {
config = _.extend( {
requiredA: value,
requiredB: value,
nestedConfig: null
// optional
optionalA: value,
optionalB: value
}, config );
config.nestedConfig = _.extend( {
requiredC: value,
// optional
optionalC: value
}, config.nestedConfig );
}
function Two( config, options ) {
config = _.extend( {
requiredA: value,
requiredB: value,
nestedConfig: null
}, config );
config.nestedConfig = _.extend( {
requiredC: value
}, config.nestedConfig );
options = _.extend( {
optionalA: value,
optionalB: value,
nestedOptions: null
}, options );
options.nestedOptions = _.extend( {
optionalC: value
}, options.nestedOptions );
}
new One( {
requiredA: value,
requiredB: value,
optionalA: value,
optionalB: value,
nestedConfig: {
requiredC: value,
optionalC: value
}
} );
new Two( {
requiredA: value,
requiredB: value,
nestedConfig: {
requiredC: value
}
},
{
optionalA: value,
optionalB: value,
nestedOptions: {
optionalC: value
}
} ); Things get more complicated if you want to require some things (like a Is the inconvenience of having twice the _.extend calls, twice the number of input parameter objects, etc. worth the potential confusion from having optional fields in If we move certain things from being options to being required, are we prepared to spend the refactoring cost to change all usages? When everything is phet-io instrumented, are we going to have: new RectangularPushButton( {
tandem: tandem.createTandem( 'button' )
}, {
// options here
} ); and take on the cost of changing core APIs that didn't previously have a config/options split and change the type signature to add config? |
I think @jonathanolson and I are on the same wavelength about this. To me the most significant factor is readability of the call site API. |
The (1) How do you propose to document which properties are required vs optional? (2) Do you intend to provide assertions for all (3) Do you propose to filter out subtype-specific properties from |
Re (3)... I anticipate that the argument for not filtering will be "we don't do it for |
I reviewed comments since #930 (comment). Everyone is in agreement about |
@samreid had spoke earlier about trying to develop a consistent pattern/tool to help with this. I definitely think that would be good to discuss. |
We decided we are happy with the plan described above, @samreid will attempt to summarize the consensus pattern in phet-info/docs/phet-software-design-patterns.md |
@jessegreenberg can you please review the change above? If all is well, please close the issue. |
Thanks @samreid. In #930 (comment) it was mentioned that assertions for should be added for required config properties. I added a statement about that, can you please re-check? |
Thanks for reminding me about the asserts, I also changed it to check for |
Very nice, thanks @samreid. Closing. |
I used this pattern in WaveInterferenceTimerNode, which has a required option. It seemed awkward to pass |
In a few places in the Area Model review phetsims/area-model-common#99 I noticed that
options
was being used for a named parameter pattern instead of a pure "options" pattern (where values are truly optional and have reasonable defaults). I have had the need to do something like this myself in a few places. I'm wondering if we should:(a) broaden our definition of "options" to include required named parameters
(b) forbid required arguments in options and move them to required/unnamed parameters
(c) come up with a new argument name for supplying named parameters. Or a strategy for coming up with custom names depending on the circumstances. Would it be parallel to options (with options still being last argument)?
(d) something else?
This does not need to be settled before Area Model review can proceed or before Area Model can be published, but it has come up enough times that I'd like to discuss it.
The text was updated successfully, but these errors were encountered: