Specifying a feature
Introduction
In the previous module, we looked at how to specify a problem in a real-world
codebase so that we can direct coding agents to help us to address it. For a
well-written issue, a lot of the time the reporter already knows what is broken
(the traceback/error message) and what the expected behaviour should be, and
our task is to make sure the issue says it clearly enough and write an
appropriate prompt to direct the coding agents to handle it.
While a lot of principles still apply, implementing a feature request with a
coding agent requires you to consider more than fixing a bug. Nobody encounters
an actual error so the coding agent does not have something to 'fix', and what
the expected behaviour should be is often a decision someone has to make.
Adding a feature with coding agents often results in subtle changes in other
parts of the code as coding agents tend to apply a change too broadly, or
simply misunderstand your feature. This module is about how to use the power of
coding agents to implement feature requests effectively.
Considerations before implementing a feature
Coding agents will happily implement any feature you or your collaborators can
think of ('say the word and I will...'). With the power of coding agents,
code can be produced quickly and it is very tempting to implement something
just because you can. It is very satisfying to see a lot of output being
'written' into your codebase with additional functionalities as it gives us a
sense of achievement and productivity.
However, the first question to ask yourself before implementing anything should
always be: should I implement this feature? Below are some things to think
about before implementing a feature:
- do I or the users really need this feature?
- what do I gain or lose without this feature?
- is this feature solving something for the majority of the users?
- is the additional complexity proportional to the potential improvement?
- who is going to maintain this feature?
- what are the potential negative impacts on the users?
As the saying goes, 'the best code is no code at all'. Every additional line of
code will introduce maintenance cost, potential bugs, and particularly when
working with coding agents, cognitive load on a scale humans are just not
able to deal with.
A case study with scikit-learn SLEP025
For the sake of the training, please refrain from reading the original proposal
and all associated discussions until the end of this section.
SLEP025: Losing Accuracy in Scikit-Learn Score
Abstract:
This SLEP proposes to rectify the default
score method for scikit-learn
classifiers. Currently, classifier.score(X, y) applies accuracy, which has
well known deficiencies (it is not a strictly proper scoring rule, and
hard-codes a 50% probability threshold). This SLEP changes the default.Motivation:
Accuracy is the most used metric for classifiers via
score(), largely
through blind default use rather than a conscious choice, and it has misled
users. The situation calls for a correction.Solution:
- Introduce a
scoringkeyword toscore(). Default stays"accuracy"for classifiers,"r2"for regressors. - Deprecate the default
"accuracy"for classifiers. - After the deprecation period, change the default to
"d2_brier_score".
Open questions: how long the deprecation period should be (proposal: 4 minor
releases instead of the usual 2, given the impact), and whether the new
default should be D2 Brier score or a model-specific objective.
Backward compatibility:
Feasible within scikit-learn's usual deprecation strategy.
Alternatives:
Remove
score entirely: forces an active choice, but is a disruptive,
unmarketable API break.Keep status quo: no disruption, but perpetuates a known bad default and
signals the project cannot correct it.
Would you vote for it?
Read the above proposal and imagine you are one of the maintainers. Would you
vote for or against the proposal? Why?
Interface, boundary and acceptance
After deciding to go ahead with a feature and use coding agents for
assistance, three areas should be considered when specifying a feature to the
coding agents:
- Interface: what should exist afterwards, such as additional parameters, the values they accept, a change in default, what are the return values etc.
- Boundary: what should not be changed.
- Acceptance: checks to make sure a plausible but wrong implementation would fail.
Suppose you have a function
fit_peak(y, x) which returns the peak centre and
there is a feature request that its uncertainty should also be returned.
Should the uncertainty be returned as a second value (i.e. a tuple) or using an
object to encapsulate it? Should a parameter such as return_uncertainty be
added and default to False? This is the interface you should think about.Current users of the function write
peak_centre = fit_peak(y, x), and if now
a tuple or a custom object is returned, how would this impact the current
users? Should this feature break existing usage or aim for backwards
compatibility instead? This is the boundary you should set.To make sure the uncertainty is correctly calculated, knowing that a value is
returned is not enough. Does the value change if the same peak is fitted twice
with different levels of noise? Is the uncertainty smaller when the noise is
lower? Are there edge cases in the calculation of uncertainty that give you
NaN? This is the acceptance you should check.If you miss out anything about the interface, boundary or acceptance when
implementing a feature with coding agents, capable coding agents are very
willing to guess, or will offer suggestions if you ask. The guess or suggestion
they make often sounds very reasonable, especially with well-versed
justification, and this is exactly where the danger is. If you do not have a
good understanding of the feature, you may be swayed into implementing
something that is subtly wrong.
Be as explicit as possible about what you would like to implement, as coding
agents do not know exactly your context and you should always be the one who
makes the decision and takes ownership of it. Taking credit when everything
is fine but quickly pointing the finger at coding agents when something is
broken is not a responsible use of this powerful technology.
A case study with SciPy issue 11841
For the sake of the training, please refrain from reading the original
issues/PR links and related discussion online until the end of this section.
The issue requested a feature to
handle missing values in a fit:
Occasionally I need to fit data that's incomplete (missing values) or perform
a fitting task over a grid of 3-D data, i.e. fitting M points for every NxN
element, where one of the elements needs to be masked out or ignored.
It may be a good feature to have a flag (or flags) to curve_fit that tell it
to ignore NaNs in an array being fit and return NaN when there are no
floats/data to fit (i.e. all nans).
I've seen documented that when you drop the check for nan/infs in curve_fit
you can silently get nonsensical results. It seems a flag here would provide
a way to define the behavior you'd like when nans/inf are present.
It may be helpful to take a look at the documentation of
curve_fit
in the last release before the feature was added. It fits a model function to
data by non-linear least squares and it refuses input containing NaN, which
is what data with missing values usually looks like.First attempt
Identify the interface, boundary and acceptance
Imagine you are going to use a coding agent to implement the above feature
request and based solely on the above text and its signature, identify the
interface, boundary, acceptance and what cannot be known from the issue
alone.
What actually happened
Below is a brief summary of the timeline when this feature was being
implemented, and it settled a lot of decisions that would have to be made if
one directed a coding agent to implement it.
- Apr 2020: the feature request is made and maintainers agree it is reasonable.
- Sep 2022 to Jan 2023: PR 16961 is submitted to implement the feature and some decisions are settled during the review:
- the flag should be named
nan_policyfollowing SciPy's convention. - the flag should accept strings with
'omit'and'raise'as options. - the flag does not take
'propagate'as an option although it is SciPy's convention as it is not clear what it means in this context. - the flag defaults to
Nonewhich keeps the existing behaviour. - the default of the existing
check_finiteflag is changed fromTruetoNone: it is treated asTruewhennan_policyis not given, and asFalsewhen it is, so an explicitcheck_finite=Truestill takes priority.
- Oct 2024: a user reports a bug in gh-21772 saying it does not correctly handle
nan_policy='omit'withsigma.'omit'drops theNaNentries inxdataandydata, but not fromsigma, so the shape is incorrect assigmashould have a one-to-one correspondence withydata. The newly added tests do not catch this as nosigmawas passed. - Nov to Dec 2024: PR 21918 fixes the above bug.
Identify them again
Now that we know much more with the benefit of hindsight, identify the
interface, boundary and acceptance, imagining you would direct a coding agent
to implement the feature.
A case study with xarray PR 9407
The specification below is a version reconstructed by
FeatBench from PR
9407, which added support for byte
attributes. The reconstructed version was used as one of the examples to
evaluate the capability of coding agents to implement something when it was
clearly specified. Treat it as what a specification could look like once
someone has already worked out what it should be.
netCDF is a file format for saving labelled and multi-dimensional data.
xarray can write it via several backends, two of which matter here: netcdf4
and h5netcdf.I want to be able to save datasets with byte attributes when using xarray's
netCDF export functionality, while ensuring compatibility with different
storage engines. Specifically, I need:
- Support for bytes as dataset attributes alongside existing supported types (strings, numbers, arrays, lists/tuples, and numpy numbers)
- Special validation when using the h5netcdf engine to ensure byte attributes are compatible with its limitations
- Automatic detection and handling of incompatible byte data for h5netcdf with clear error messages
When I save datasets using the h5netcdf engine, I want the system to:
- Check if any attribute values are bytes
- Verify that these bytes can be decoded as UTF-8 strings without errors
- Ensure the byte data contains no null characters (zero bytes)
- Provide helpful error messages suggesting alternative engines if the bytes are incompatible
- Allow valid UTF-8 encoded bytes without null characters to be saved successfully
For other engines like netcdf4, I want byte attributes to be accepted without
these additional validations, maintaining the existing behavior.
I also want the validation to properly handle the existing invalid_netcdf
parameter behavior, ensuring that numpy boolean types are only allowed when
both invalid_netcdf is True and the engine is h5netcdf, while maintaining all
existing validation rules for other data types.
The system should validate attributes throughout the entire dataset structure,
including both dataset-level attributes and those on individual data arrays,
providing consistent error messages that clearly identify which attribute name
and value caused any validation failures.
Identify interface, boundary and acceptance
Identify its interface, boundary and acceptance, with a particular focus on
boundary.
Conclusion
- Decide yourself and/or with maintainers/users whether the feature should exist at all before specifying and implementing it.
- Interface is about what must exist afterwards (new parameters, accepted values, defaults, returns).
- Boundary states what must not be changed and where the new behaviour must not apply. Coding agents tend to change everything that looks relevant and it relies on you to specify it correctly.
- Acceptance checks both what must now succeed and what must still be rejected. Merely passing tests and not raising an error do not mean it is really working.
- Changing a public behaviour requires a clear migration/deprecation plan for users who never asked for it.