Skip to content

Proposal: API audit #7460

Open
Open
@GregStanton

Description

Increasing access

This proposal would make p5.js more beginner friendly, in three stages:

  1. Establish consensus on a set of criteria and guidelines for the API.
  2. Resolve complications in the current API.
  3. Implement a simple process for preventing complications in the future.

Which types of changes would be made?

  • Breaking change (Add-on libraries or sketches will work differently even if their code stays the same.)
  • Systemic change (Many features or contributor workflows will be affected.)
  • Overdue change (Modifications will be made that have been desirable for a long time.)
  • Unsure (The community can help to determine the type of change.)

What's the problem?

The p5.js API contains quite a few inconsistencies and other forms of complexity. This complexity makes p5.js harder to learn and harder to use, since users experience it directly through its API. While some parts of the API have been simplified for 2.0, such as the API for vertex functions, other complexities remain. This is because identifying and preventing complexities in a large API is difficult without clear guidelines and a practical quality assurance process.

What's the solution?

1. Establish criteria and guidelines

In order to identify and prevent unnecessary complexity in the p5 API, we can develop a list of criteria we want it to satisfy. We can also provide guidelines for common types of features, such as getters and setters. Initial criteria and guidelines are listed below.

Criteria

  1. Consistency: Provide a similar interface for similar features and different interfaces for different features.
  2. Readability: Design features that are easy to read and understand.
  3. Writability: Make features easy to use and hard to misuse.
  4. Predictability: Respect the principle of least surprise, so that users can guess how features work.
  5. Extensibility: Hide implementation details and lay a foundation for add-on libraries.
  6. Economy: Limit redundant features, and consider add-on libraries for new features.

For a given feature, these design criteria may be considered with respect to naming, syntax, and semantics.

Guidelines

  • Getters and setters:
    • A function or method with the same name as a setting, such as stroke() and fill(), should act jointly as a getter (when no argument is passed) and a setter.
    • General setters such as vertexProperty(name, value) should act as getters when value is omitted.
    • For consistency with commonly used functions such as stroke(), joint getters/setters should be used when possible. However, if separate getters and setters are to be used, they should begin with “get” and “set,” except for getters for Booleans, which should have names such as isLooping().
    • Something about setting fields directly? (Guidance might prevent inconsistencies like using the field disableFriendlyErrors to toggle friendly errors and using functions like fullscreen() and autoSized() to toggle other settings.)
  • Other sets of guidelines...

During the second phase, when specific kinds of issues are dealt with, the guidelines can be fleshed out.

References:

2. Perform an audit (a scavenger hunt!)

We could hold a scavenger hunt for features that don't adhere to our API criteria. Beginners could make significant contributions by spotting naming inconsistencies, for example. To get the ball rolling, I performed a rapid audit of the full API. The methodology and a partial sample of results are described below.

Methodology

I skimmed through the whole reference and looked for issues that jumped out to me as potential areas of concern (I didn’t systematically apply all criteria to each feature). I also skipped over some things that I didn’t suspect were problematic.

Caveats:

Results (partial sample)

For illustration, only a partial sample of results from the rapid audit are included here. Skimming this section should give a rough sense of what's at stake.

Note: The sample includes examples of potential problems with naming, syntax, and semantics (broadly defined). Each example is tagged with the criteria that fail to be satisfied.

This partial sample of API problems can be replaced with separate sub-issues, with one issue for each affected section of the reference.

Note: The sample only includes issues that may require breaking changes, but the audit did reveal other issues that could be fixed with non-breaking changes after the release of p5.js 2.0.

Naming
  • color()
    • Relevant criteria: Consistency, predictability
    • Problem: Unlike other constructor functions in p5, this function does not start with a “create” prefix, leading users to guess that it’s a setter such as stroke().
    • Potential fix: Replace color() with createColor().
  • getTargetFrameRate()/frameRate()
    • Relevant criteria: Consistency, predictability
    • Problem: In this pair of functions, different names are used for the same value. To get the target frame rate, users call getTargetFrameRate(), but to set the target frame rate, they use frameRate(). Also, unlike a joint getter and setter such as pixelDensity(), frameRate() gets a different value than it sets (it sets the target frame rate but gets the calculated frame rate).
    • Potential fix: Replace these features with idealFrameRate() (gets and sets the ideal frame rate) and calculateFrameRate() (calculates the actual frame rate, similar to calculateBoundingBox()). An alternative is to replace "ideal" with "target"; however, when targetFrameRate() is compared with calculateFrameRate(), "target" could be interpreted as a verb instead of an adjective, which may cause confusion.
  • setAttributes()
    • Relevant criteria: Consistency, readability, predictability
    • Problem: This feature has a couple of issues. First, it has a plural name, but setAttributes(key, value) only sets a single attribute. Second, the name is ambiguous, since it only sets attributes of the WebGL drawing context, but this isn't advertised in the name.
    • Potential fix: Both of these issues could potentially be fixed by replacing setAttributes() with, say, canvasProperty(key, value).
  • onended()
    • Relevant criteria: Consistency, readability
    • Problem: This feature’s name doesn’t use camel case, the name is awkward linguistically, and it’s inconsistent with similar methods of p5.Element, like touchEnded().
    • Potential fix:: Replace onended() with playEnded(). (However, there is a deeper issue, which is that these functions take a callback function, whereas event handlers like doubleClicked() are defined directly by the user. If one of these two behaviors cannot be used in all cases, then they should be distinguished by their names. For example, a name like onTouchEnd() could be used for one type of behavior and a name like touchEnded() could be used for another.)
Syntax
  • createModel(), loadModel()
    • Relevant criteria: Consistency, readability, writability
    • Problem: The same parameters in these functions appear in different orders (normalize and fileType are out of order in one signature, fileType is out of order in another, and fileType is missing from one signature).
    • Potential fix: One option is to make the parameter lists consistent. Another is to choose a different API that allows the parameters to be specified in any order (and there are potentially multiple such APIs).
  • spotLight(), directionalLight() and pointLight()
    • Relevant criteria: Readability, economy
    • Problem: As an example, spotLight() has eight signatures. It accepts three vectors as input, but in some signatures, some of these vectors are specified as p5.Vector objects whereas others are not. This makes the documentation unnecessarily complicated and leads to code that’s hard to read. The other listed features have the same type of issue.
    • Potential fix: A potential fix is to remove the signatures that mix componentwise specifications and p5.Vector specifications. The pure componentwise signature of spotLight() has 11 parameters and is very hard to read, but keeping both the pure componentwise and pure p5.Vector signatures reduces breaking changes and keeps these features consistent with other functions that take multiple vectors as input, such as quad(). (Since quad() takes up to a whopping 14 parameters, and since point() already accepts both componentwise and p5.Vector input, it makes sense to add support to quad() and all other 2D primitive functions for both types of input.)
  • p5.Graphics, p5.Framebuffer
    • Relevant criteria: Consistency, predictability
    • Problem: The reference says these classes are “similar,” and it’s true. Semantically, they are similar. However, syntactically, they’re significantly different. Requiring users to learn different interfaces for the same kinds of features reduces accessibility. If these aren’t reconciled, complications are likely to compound (e.g. the p5.Shape class developed for 2.0 has an interface that’s closer to that of p5.Graphics, and it may be exposed to users in a future version).
    • Potential fix: Requires discussion.
Semantics
  • addClass(), class()
    • Relevant criteria: Readability, economy
    • Problem: The p5.Element class has addClass() and class(), both of which add a class to the element. The difference is that class() is a joint getter/setter that seems to make addClass() unnecessary. Having both is likely to confuse users.
    • Potential fix: Remove addClass().
  • millis()
    • Relevant criteria: Consistency, readability, predictability
    • Problem: Unlike all other time and date functions, millis() returns the time since the sketch started running; all other functions, including second(), return the current time (e.g. if a sketch is run 32 seconds after noon, then when the sketch starts running, second() will return 32 whereas millis() will return 0). Also, “millis” is an abbreviation, unlike the other function names; that doesn’t seem like enough to convey the difference in behavior.
    • Potential fix: Requires discussion.
  • debugMode()
    • Relevant criteria: Readability, writability, extensibility
    • Problem: This function has multiple signatures, which take up to nine numerical parameters. Extending this feature is desirable but impractical, given that it’s already complex. Also, “mode” is a problematic description of this feature, since it actually provides different helpers (a grid helper and an axes helper) which can be combined, rather than modes, which are typically mutually exclusive.
    • Potential fix: The three.js library provides a GridHelper, an AxesHelper, and a variety of other helpers (a camera helper, a spotlight helper, etc.). In p5, having separate axisHelper() and gridHelper() functions should be an improvement. We might also consider functions like axesHelperProperty(key, value) for greater readability and writability (effectively allowing named parameters).
  • 2D Primitives
    • Relevant criteria: Consistency, predictability
    • Problem: Whereas quad() has multiple signatures and can create a shape in 2D or 3D, triangle() only works in 2D. Other issues include shapes that can be drawn in WebGL mode but only by specifying xy-coordinates rather than xyz-coordinates. Some of these shapes support a detail parameter while others don’t. Some support p5.Vector arguments and others don’t. Rounding corners in rect() and square() adds another complication.
    • Potential fix: The first thing is to make a table with one row per feature, and columns for dimensions, vector arguments, and so on. This will help us identify the exact issues that need to be addressed, and which of them would require breaking changes to fix (@davepagurek and I mostly finished the table).

3. Implement quality assurance process via issue templates

Bugs can be fixed in any version of p5. API problems cannot be fixed outside of a major version release (if at all), if they're like any of the problems listed above. To avoid introducing such problems in the future, we can implement a basic quality assurance process via the issue templates for new features and existing feature enhancements:

  1. Include a section for code examples. The community can help with filling this in, so that early-stage ideas are not discouraged.
  2. Include a checklist, to be completed before releasing the feature and closing the issue. The checklist can indicate criteria and guidelines. For each checklist item, “good” and “bad” examples can be provided (either directly on the issue template, or via a link to the contributor docs).
  3. Indicate what to expect after submitting a new-feature request:
    a. Documentation will be required before release (and encouraged before implementation).
    b. Review will be required by multiple stakeholders.

Volunteers

There are various ways volunteers might help! Specific guidance can be provided once separate sub-issues have been created for the various parts of this proposal. For now, here are some examples of work that would need to be done:

  • Create a complete list of all abbreviations in the p5.js API
  • Create a complete list of all getters and setters
  • Submit problems using the same format as above (relevant criteria, problem, potential fix)
  • Implement changes in the source code (further in the future)

If you're interested in helping with this issue, you can leave a comment, and I can add your username here. Once the sub-issues have been created, I'll add a comment on this issue and tag everyone listed to let them know.

@dhruvinjs, @ImRAJAS-SAMSE, @Vaivaswat2244, @Jai2082001

Pros (updated based on community comments)

  1. Consistency: Inconsistencies will be fixed or prevented.
  2. Readability: Confusion about the meaning of code will be reduced or prevented.
  3. Writability: Features will be easier to use and harder to misuse.
  4. Predictability: Unpleasant surprises will be eliminated or prevented.
  5. Extensibility: Features will be more extensible, both for the core p5 library and for add-ons.
  6. Economy: Confusion or cognitive overload from redundant features will be reduced or prevented.

Cons (updated based on community comments)

  1. Breaking changes: Existing code may break. (However, we can reduce the impact in various ways. For name changes, we could implement the new names as aliases, mark the old features as @deprecated if they're scheduled for removal, and then move them into a Deprecation module. Deprecated features would then be listed in a dedicated section in the reference. For changes to syntax and semantics, we could develop a compatibility add-on.)
  2. Time expenditure: Resolving this issue will take time. (However, we can speed it up by inviting more contributors. This issue is largely beginner friendly, as noted above in the Volunteers section.)

Proposal status

Under review

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions