# Adaptoring: Adapter Generation to Provide an Alternative API for a Library

Lars Reimann  Institute of Computer Science III
University of Bonn, Germany
Email<EMAIL_ADDRESS>Günter Kniesel-Wünsche  Institute of Computer
Science III
University of Bonn, Germany
Email<EMAIL_ADDRESS>
###### Abstract

Third-party libraries are a cornerstone of fast application development. To
enable efficient use, libraries must provide a well-designed API. An obscure
API instead slows down the learning process and can lead to erroneous use.

The usual approach to improve the API of a library is to edit its code
directly, either keeping the old API but deprecating it (temporarily
increasing the API size) or dropping it (introducing breaking changes). If
maintainers are unwilling to make such changes, others need to create a hard
fork, which they can refactor. But then it is difficult to incorporate changes
to the original library, such as bug fixes or performance improvements.

In this paper, we instead explore the use of the adapter pattern to provide a
new API as a new library that calls the original library internally. This
allows the new library to leverage all implementation changes to the original
library, at no additional cost. We call this approach adaptoring. To make the
approach practical, we identify API transformations for which adapter code can
be generated automatically, and investigate which transformations can be
inferred automatically, based on the documentation and usage patterns of the
original library. For cases where automated inference is not possible, we
present a tool that lets developers manually specify API transformations.
Finally, we consider the issue of migrating the generated adapters if the
_original_ library introduces breaking changes. We implemented our approach
for Python, demonstrating its effectiveness to quickly provide an alternative
API even for large libraries.

###### Index Terms:

API design, API evolution, Adapter pattern, Wrapper library, Refactoring,
Adaptoring

††publicationid: pubid:
Accepted at the Int. Conf on Software Analysis, Evolution and Reengineering
SANER 2024, March 12–15, 2024, Rovaniemi, Finland
©2024 IEEE. Personal use of this material is permitted. Permission from IEEE
must be obtained for all other uses, in any current or future media, including
reprinting/republishing this material for advertising or promotional purposes,
creating new collective works, for resale or redistribution to servers or
lists, or reuse of any copyrighted component of this work in other works.

##  I The API Evolution Challenge

Using third-party libraries allows application developers to focus on their
core business logic. They no longer have to reinvent the wheel, to solve
recurrent issues like the implementation of an encryption algorithm, but can
instead rely on an existing and well-tested solution. To interact with a
library, application developers need to understand its application programming
interface (API), which places a particular emphasis on the learnability and
usability of said API [1, 2, 3]. Often, however, the API is complex and
suffers from design issues that crop up in software developed by many
contributors over many years [4, 5]. The maintainers of the affected library
can remedy these problems in different ways, summarized in Fig. 1 and
discussed below.

##  II State of the Art

#### API refactoring

The straightforward way of improving an API is to change the original library
directly. The systematic way of direct change is via refactorings. In the
second edition of his book, Fowler [6] devotes an entire chapter to API
refactoring. Dig and Johnson [7] analyzed API changes and found that 80% of
them were basic refactorings. Koçi et al. [8] similarly classified API
changes. IDEs like Eclipse111https://www.eclipse.org/ide/ or IntelliJ
IDEA222https://www.jetbrains.com/idea/ offer various automated refactorings
that can be used to alter an API. For broader coverage of the vast refactoring
literature, we refer to prior surveys [9, 10, 11].

(a) Refactoring, drop the original; breaking clients

(b) Refactoring, drop the original, migrate clients

(c) Refactoring, keep the original but deprecate it

(d) Hard-forking (two decoupled code bases)

(e) Adaptoring (two APIs sharing original code base)

Figure 1: Discussed approaches for creating a new API for a library. Blue
boxes represent the original library and green boxes the new one. Original
users () interact with the original function, unless they migrate their own
code, while new users () directly use the changed function. Adaptoring builds
a new API based on the unmodified, original one.

#### Automated migration of client code

After changing an API, client code might have to be adjusted. To avoid tedious
and error-prone manual adjustment, Perkins [12] suggests that API developers
provide code to replace calls to deprecated methods. Henkel and Diwan [13]
capture refactorings during development of the API, which can then be replayed
for the client code. Similarly, Hora et al. [14] monitor changes to the API of
a library as its source code is altered. Xing and Stroulia [15] detect changes
automatically without additional work from the API developers and derive
plausible replacements for client code. In a case study on an Android API,
Lamothe and Shange [16] explore the use of automated API migration techniques
and report that determining the types of new parameters is still a challenge
for the reviewed approaches.

#### Keep deprecated API

Breaking changes and migration issues can be deferred, by temporarily keeping
the old API as methods that forward calls to the new API. Typically, these
forwarding methods are deprecated. The deprecation message lets users schedule
migration and can include migration guidance. However, Kao et al. [17] point
out that “API deprecation entails significant cost to API developers”
including “code bloat […] and serious threats to security, performance, or
code quality [and] the cost of migration guide provision.” In addition, _new_
clients need to deal with an unnecessarily large API, which impedes learning
and correct use.

#### Hard forking

If library maintainers are not willing to change the API in any of the ways
above, other developers need to create a hard fork of the library that they
can then refactor. This, however, decouples the fork from the original
library, making the integration of future changes difficult.

#### Adapter generation

A different way to provide a new API for a library is proposed by Jugel [18]:
It allows deleting and renaming API elements of a Java library in a UML
editor. Afterward, a transformation is applied to create a DSL-like API using
the builder pattern [19]. Finally, code gets generated to implement the API
using the adapter pattern [19].

##  III A New Approach: Adaptoring

Fig. 1 summarizes the different approaches for creating an alternative API
that we discussed. Among the reviewed approaches, the use of the adapter
design pattern [19] is the only way to provide a new API for a library without
breaking clients (Fig. 1(a)), raising migration issues (Fig. 1(b)), bloating
library code by deprecation (Fig. 1(c)), or completely duplicating library
code (Fig. 1(d)).

However, we only found a single prior work that suggests an adapter-based
approach [18]. Unfortunately, it suffers from several limitations:

  * •

No implementation of the approach is publicly available.

  * •

The only supported API transformations are renamings and deletions, although
other API transformations can be realized by adapters (e.g. moving an API
element).

  * •

To quickly create a wrapper library for a large library, automation is vital.
However, [18] does not automatically infer potential transformations.

  * •

The usability of the editor for manual API transformation is lacking. This was
criticized by the author himself.

  * •

There is no support for the evolution of the adapter layer after breaking
changes to the original API.

We think that it is worthwhile to explore ways to overcome the above-mentioned
limitations because an adapter-based approach has clear advantages, for
library maintainers as well as application developers:

_Library maintainers_ can use an adapter layer to provide an alternative API
that is clearly separated from the original API. New clients can directly use
the self-contained alternative API, while existing clients have time to
migrate from the original API to the alternative one, perhaps aided by
automated migration approaches. They can additionally support separate APIs
for different user profiles: For example, the parts of an API intended to
enable extensions might not be needed by application programmers, who are
interested in using the library for their business logic. Likewise, novices
would benefit from a smaller API that provides only commonly needed API
elements. This accelerates development and can prevent misuses resulting from
misunderstanding the function of overwhelmingly many API elements [20, 21].

_Application developers_ can use an adapter layer as an alternative to hard
forking, if the maintainers do not want to create it themselves: Since the
original library is called by the adapters, any implementation changes, like
bug fixes or performance improvements, can be consumed without changes to the
wrapper library. Only changes to the original API, require follow-up changes
to the related adapters. The adapter layer can be made available to other
developers, for example for in-house development, so the effort to create it
initially need not be repeated.

In the remainder of the paper, we will elaborate _adaptoring_ , as a
refinement of the adapter-based approach that overcomes some of the
limitations of [18]. Adaptoring creates a wrapper library to provide an
alternative, more usable API for an existing library (Fig. 1(e)). This leads
us to our main research questions:

  * •

RQ 1: Which API transformations can be supported? (Sec. IV)

  * •

RQ 2: How can API transformations be inferred automatically? (Sec. VI)

  * •

RQ 3: How can the manual specification of API transformations be eased? (Sec.
VII)

  * •

RQ 4: How can the adapter code be evolved if the original library changes?
(Sec. VIII)

##  IV Adaptorable API transformations

We call an API transformation that can be implemented by using the adapter
pattern [19] adaptorable. Via an adaptorable transformation, an original API
element is

  * •

left out,

  * •

moved to another location,

  * •

modified regarding its name, parameters, parameter types, preconditions, or
language-specific attributes (such as the Python-specific optionality of
parameters)

or a new API element is created that provides the desired API by invoking
functions of the original library. As additional code besides the calls to the
original library, we only allow the validation of preconditions and code that
is syntactically required to achieve the desired new API, such as a new class
that bundles several functions. No new behavior may be added.

We use Fowler’s refactorings [6], constrained to refactorings that actually
change the API, as our base catalog of systematic API transformations.
However, not all the API refactorings can be handled by adaptoring.
Particularly, we can only add new API elements whose implementation can be
based on the original API. This excludes refactorings like Extract
Function333We assume the extracted function becomes part of the public API. or
Replace Conditional with Polymorphism. All API transformations that can be
achieved by adaptoring can also be achieved by refactoring, since we could
also add the adapter to the original library, inline the calls, and replace
the original function with it. Thus, the adaptorable API transformations are a
subset of the API transformations that refactoring can accomplish. This still
leaves a substantial set of API-transforming refactorings that _can_ be
realized by adaptoring (RQ 1):

  * •

API elements can be removed by simply not creating adapters for them. This
way, useless or redundant elements can be eliminated, which decreases the size
of the API and helps the user focus on the relevant parts. The removal also
accelerates the redesign, since these API elements need not be changed
further.

  * •

Parameter lists of callables can be reordered, or language-specific attributes
like optionality and the default value of parameters can be changed.

  * •

Preconditions can be enforced, e.g. by introducing assertions444Since
additional exceptions are thrown when preconditions are violated, this is an
API change. or using narrower types for function parameters. This makes the
API more robust.

  * •

API elements can be renamed to a more understandable name. This also helps
their discoverability.

  * •

API elements can be moved so that they are close to related API elements and
can be found more easily.

  * •

API elements can be pushed to subclasses if they are only relevant for some of
them.

  * •

API elements can be repackaged (e.g. Introduce Parameter Object or Replace
Function with Command).

#### Implementation for Python

To test our approach, we implemented a prototype that can apply various API
transformations to Python libraries and generate adapters for them (Sec. V).
So far, our prototype implements the adaptorable API transformations shown in
Table I. For many of them, we implemented automated suggestions (Sec. VI), as
indicated by the “Inferred” column. The “Not yet” for the Move transformation
means that approaches to infer moves [22, 23, 24, 25, 26] exist and could be
integrated. We will discuss other features of the prototype in the next
sections.

TABLE I: API Transformations Implemented for Python Transformation |  Semantics | Inferred
---|---|---
Deletions (Sec. VI-A)
Remove |  Remove class or function | Yes
Replace with constant |  Remove parameter from new API and internally replace references to it by a constant | Yes
Optionality of Parameters (Sec. VI-B)
Make optional |  Make parameter optional and specify its default value | Yes
Make required |  Make parameter required | Yes
Enforcement of Preconditions (Sec. VI-C)
Add bounds check |  Ensure parameter value is within an interval | Yes
Replace with enum |  If a parameter can take only a finite set of strings, encode these values in an enum and replace the string parameter with a parameter that accepts instances of this enum | Yes
Other Design Changes
Rename |  Rename declaration | No
Move |  Move declaration | Not yet
Group |  Group parameters into parameter object [6] | No

##  V Adapter Generation

Creating adapters [19] for a large library manually is tedious, error-prone,
and time-consuming and should, therefore, be automated. Given a list of
adaptorable transformations, we use the following five-step process to
generate adapters:

  1. 1.

Collect a list of all API elements of the original library, including classes,
functions and their parameters.

  2. 2.

Create an abstract syntax tree (AST) that represents trivial wrappers around
each function of the original library. Trivial wrappers apply no changes yet.
Each contains just a call to the function with the exactly same signature from
the original library. Compared to refactoring [6], the forwarding direction is
inverted: When adaptoring, the new function calls the old one, while when
refactoring, the old function can be kept to forward to the new one to prevent
breaking changes.

  3. 3.

Adjust this AST by applying the specified API transformations one by one. This
may delete the wrapper altogether, change its signature (name, parameters), or
change its implementation. Implementation changes include changes to the
arguments of the call to the original library and insertion of argument
checking operations.

  4. 4.

Post-process the resulting AST to ensure correctness of the output created in
the next step. In this step, we can enforce language-specific constraints,
which may be violated after applying the API transformations.

  5. 5.

Generate code by visiting the nodes in the AST and serializing them. This
creates code for all adapters that were not removed, including unchanged ones,
so no interaction with the original library is necessary for users of the new
one.

#### Implementation for Python

In our Python prototype, the adapter generator implements the algorithm
outlined above. To collect API elements in Step 1, we use
astroid555https://github.com/pylint-dev/astroid. So far, the prototype only
deals with classes, functions, and parameters (differentiating between
required and optional parameters), but can be extended easily to other kinds
of API elements such as fields. In Step 4 (post-processing), we reorder
parameters to ensure that all optional parameters are listed after all
required ones, which is mandated by Python. Finally, in Step 5 we also include
the docstrings of the corresponding API elements of the original library in
the generated code, without the sections referring to removed API elements.

##  VI Inferring API Transformations

Adapter code generation can be automated as described above, given a list of
adaptorable API transformations (Sec. V). This leaves the specification of
such a list as the remaining problem. In this section, we discuss the
automated inference (RQ 2) of deletions (Sec. VI-A), parameter optionality
(Sec. VI-B), and precondition checks (Sec. VI-C). In Sec. VII we discuss
manual specification of transformations. We limit our discussion to classes,
functions, and parameters (differentiating between required and optional
parameters), but the notion can be extended to other kinds of API elements
such as fields.

###  VI-A Inferring Deletions

#### Unused elements

Our first focus is the reduction of an API’s size, to limit the amount of work
necessary in subsequent steps. Our analysis counts usages of API elements in
an (ideally large) set of client programs:

  * •

A usage of a class $c$ is a call to a method of $c$.

  * •

A usage of a function $f$ is a call of $f$.

  * •

A usage of a parameter $p$ is the explicit setting of the parameter $p$ when
the containing function is called.

Counting usages of an API element across all analyzed client programs yields
the usage count of that element. We call elements with usage count $0$ unused
and all others used. Unused elements can be removed without breaking any of
the analyzed client programs666Other client programs that we did not analyze
might still use the API element, so element deletion must still be considered
a breaking change..

#### Useless elements

For _new_ clients, we can disregard backward compatibility, enabling further
elimination of used but useless parameters. We start identification of useless
parameters by counting how often a parameter $p$ is set to a particular value
$v$. We do not distinguish whether $p$ is set to $v$ explicitly, or whether
$v$ is set implicitly as the default value of an optional parameter if the
language permits. Based on the parameter value counts, we identify the most
common value for each parameter and define the usefulness of a parameter $p$,
as the number of times $p$ has been set, explicitly or implicitly, to a value
_different from_ the most common one. We call a parameter useless if it is
always set to the same value. A useful parameter is set to at least two
different values. Each useless parameter can be eliminated from the respective
function and set to its unique constant value internally, in the
implementation of the function. This simplifies the API for new clients, but
is a breaking change for existing client programs that set the parameter
explicitly.

#### Rarely used and almost useless elements

By eliminating useless parameters, we create an API that will serve new
clients well but will not be backward compatible. Once we have crossed this
line, further reductions can be achieved for new users. To this end, we
generalize the notion of unused and useless API elements: Given a threshold
$T\geq 0$, we call an API element rarely used if its usage count is less than
$T$. Likewise, we call an element almost useless if its usefulness is less
than $T$. In this context, we define the usefulness of classes and functions
to be equal to their usage count. To achieve an even stronger size reduction,
almost useless elements can be additionally removed from the API.
Alternatively, we could define a basic API version for novices and an expert
version of an API, as suggested by [27], using different thresholds. The terms
unused and useless describe the special case $T=1$. Setting $T=0$ yields the
original API, without any changes.

#### Implementation for Python

In our prototype, we analyze client code statically using astroid to build an
AST, which is then traversed to find and count calls. astroid is also used to
derive call targets, where possible (this is non-trivial, in Python).
Afterward, class, function, and parameter usages are counted as described
above. To compute value counts for a parameter, all occurrences of non-literal
values are differentiated. This prevents wrongly marking a parameter as
useless (see Sec. VI-A) if it is always passed a reference to the same local
variable x, which could refer to many different values, particularly in
different client programs. No partial evaluation is implemented currently, so
the expressions 2 and 1 + 1 are seen as different values. After counting, API
elements with a usefulness below the set threshold are marked for deletion
(see Sec. VI-A). The entire analysis is available as part of the library-
analyzer Python package777https://github.com/Safe-DS/Library-Analyzer.

Derivation of the call target is challenging in Python due to its dynamic
features. Better results could be obtained by dynamic analysis. For our
prototype, we chose static analysis primarily for security reasons, so that
users do not run thousands of client programs mined from the internet on their
machine. Moreover, in our application to data science, client programs often
take minutes to several hours to run, which makes dynamic analysis
prohibitive.

###  VI-B Inferring Optionality of Parameters

Python and other programming languages let developers mark parameters as
required or optional. The choice between making a parameter required or
optional is a trade-off between correctness and usability. More optional
parameters mean less effort for programmers, who do not need to explicitly set
a value. More required parameters mean less risk that developers might not
notice that values other than the default one would be more appropriate.
Therefore, defining default values for parameters needs thoughtful
consideration and should only be done if there is a clear most common value,
in the first place. For example, a boolean parameter that is almost equally
often set to True and False should not be optional but required, to force
developers to choose consciously.

Our approach is to make a parameter optional only if we have sufficient
empirical evidence that it has a clear most common value. For this, we do a
_null-hypothesis significance test_ [28, p. 923] by looking only at the two
most common values of a parameter $p$, let’s call them $v_{1}$ (most common)
and $v_{2}$ (second most common). Let $uc_{1}$ and $uc_{2}$ respectively
denote how often $v_{1}$ and $v_{2}$ are used (explicitly or implicitly), and
$n:=uc_{1}+uc_{2}$. We then assume the null hypothesis $H_{0}$ that in the $n$
cases where either value was used, users chose $v_{1}$ and $v_{2}$ equally
often. If we can reject $H_{0}$, we make the parameter optional with $v_{1}$
as its default value. Otherwise, we make it required. The probability
distribution underlying $H_{0}$ is a binomial distribution [28, p. 263] with a
success chance of 0.5, and we reject $H_{0}$ if its p-value [29] is less than
or equal to a significance level $\alpha$ in a two-sided test888Due to the
symmetry of the binomial distribution, this is equivalent to a one-sided test
with a significance level of $\frac{\alpha}{2}$.:

$2\sum_{i=uc_{1}}^{n}{{n\choose i}0.5^{n}}\leq\alpha$

The significance level $\alpha$ can be tuned to produce a suitable result. A
lower $\alpha$ produces more required parameters, while a higher $\alpha$
produces more optional parameters. The formula is designed so that parameters
in rarely used functions (as long as we don’t have more data about actual
usages) are more likely to be made required, opting for safety in this case.
It prevents breaking changes when the default value is changed or removed.
Usability is not affected noticeably, since these functions are rarely used,
anyway.

#### Implementation for Python

The value count analysis of the library-analyzer package that we used to
delete (almost) useless parameters (Sec. VI-A) already provides the data we
need for this inference step. The API redesigner can set the significance
level, $\alpha$ and the suggestions for the optionality of parameters are
created based on the above formula. The limitations of static analysis also
apply here: In particular, we never change the optionality of a parameter if
the most common or second most common values are non-literals (e.g. variable
references), since we do not know their actual value.

###  VI-C Inferring Enforcement of Preconditions

Preconditions can also inspire API transformations, as shown in the following
non-exhaustive list. All can be enforced by the addition of runtime checks in
the generated adapters. More elegant, statically verifiable, language-specific
implementation options, are mentioned below for each particular case. A well-
designed API should use these language features directly, but they might
simply not have been available when that part of the API was written:

#### Numeric Intervals

Values of a numeric parameter might have to be in an open, closed or mixed
interval, e.g. $\left[0,1\right]$. If the type system of the programming
language or external linters permit, this requirement can be expressed as a
refinement type [30]. Then its implementation boils down to putting the
stricter parameter type into the adapter’s signature.

#### Enumerations

A string parameter might only accept a finite set of values. This could be
expressed by an enumeration in many languages. Among other benefits, the use
of an enumeration improves auto-completion. This solution requires the
generation of the additional enumeration type, the replacement of the
parameter type in the adapter signature, and the translation of the
enumeration values into the values expected by the original API.

#### Parameter dependencies

Sometimes, parameter values depend on each other so that not all combinations
of parameter values are legal or useful. For instance, a function to serialize
an object to JSON could have a parameter that controls whether everything
should be dumped into a single line or spread over multiple ones, and another
parameter to control indentation. The latter only makes sense in the multiline
case. Dependent parameters can be avoided by creating an abstract class
JSONFormat with two subclasses Singleline (with a parameterless constructor)
and Multiline (with a constructor parameter to control indentation). The
original two parameters then get replaced by a new one of type JSONFormat.

#### Inference from documentation

This raises the challenge of inferring the real API of a function, contrasting
it to the one expressed in the function declaration, and deriving a suitable
transformation. Our solution is based on static analysis of the documentation
of each function in an API. We take advantage of structured documentation
formats to break down documentation into pieces that can be analyzed more
easily.

#### Implementation for Python

An added challenge of Python docstrings is that there is no single standard
format, but many competing formatting proposals, including reStructuredText
[31], Numpydoc [32], Google’s style [33], or Epytext [34]. Fig. 2, for
example, shows an excerpt of a docstring for a class in Numpydoc format. It
contains a structured description for each parameter of the constructor, with
identifiable sections for its name (before the colon), type and default value
(after the colon), and a natural-language description (indented in the line
below).

Figure 2: Example of preconditions hidden in a documentation snippet in
Numpydoc format.

To implement subsequent steps independently of the docstring format, we first
transform them into a common structured representation using the docstring-
parser999https://github.com/rr-/docstring_parser. Afterward, we identify
regions that can contain specific preconditions, based on additional textual
structure. E.g. the type field (yellow/gray in Fig. 2) can contain any text,
but generally has a relatively fixed format. The set notation {'l1', 'l2'}
indicates that only these two string literals are legal. This is the clue to
suggest the introduction of an enum. The name of the enum is derived from the
name of the class/function and parameter, while the names of its instances are
derived from the listed values.

We further analyze the corresponding natural-language description with the
rule-based matching of spaCy [35]. For instance, when one of our rules finds
the blue text in Fig. 2, we derive that the parameter C must be greater than
0. Once sufficient training data is available, the rule-based matching can be
replaced by an ML model to improve generalization.

So far, we only inferred preconditions from docstrings. Analysis results for
numeric intervals and enumerations can be improved by including the results of
our client code analysis (Sec. VI-A and VI-B), as suggested by Wang and Zhao
[36].

##  VII API Editing

The automatic inference described in Sec. VI cannot detect all possible API
transformations. Moreover, we cannot generally guarantee that a suggested
change truly leads to a better API. Ultimately, the redesign of the API is the
responsibility of a human. The inferred API transformations can be used as a
starting point but require careful review and extension by non-inferrable
transformations.

To facilitate the review and extension process (RQ 3), we developed a
graphical user interface (GUI) that makes it easy to (1) explore the API
elements, including their documentation and collected usage data, preventing
unnecessary context switches e.g. to the online API documentation, (2) view
the transformations suggested for each element, (3) change any transformation
and add more, and (4) filter the list of API elements to narrow it down to
relevant ones.

#### Implementation for Python

Our proof-of-concept implementation for Python includes a convenient, browser-
based GUI, the API Editor101010https://github.com/Safe-DS/API-Editor. In the
API Editor, we express API transformations as annotations attached to
individual API elements. Annotations can also capture changes that are not API
transformations, e.g. to alter docstrings.

Figure 3: Excerpt of the GUI of the API Editor. Users can browse through the
navigator on the left-hand-side, filter the displayed elements via the input
field at the bottom, inspect the annotations of the selected element, and edit
annotations. The image shows a selected function and its first parameter.

Fig. 3 shows the main panel of the API Editor111111Some GUI elements were
hidden for brevity.. The tree view on the left side shows the different API
elements. The selected element (here the function recall_score) is marked in
blue. Details about the selected element are shown in the selection view, on
the right-hand-side. For a function, the selection view displays its
documentation, and its parameters. The annotations that are attached to an API
element are shown underneath its name, where they can be edited via the button
with the wrench symbol or deleted via the trash can symbol. Users can add more
annotations by clicking on the “Annotations” dropdown next to each element.
This opens an annotation-specific editing dialog. For instance, users can
specify the new name when editing a Rename transformation.

Additionally, the selection view can be used for reviewing the automatically
generated annotations or annotations created by a co-developer in a
collaborative environment: Individual annotations can be marked as “Correct”,
“Unsure”, or “Wrong”. Marking an annotation as “Unsure” is a helpful reminder
to revisit it later. Marking an annotation as “Wrong”, instead of deleting it,
prevents adding it again and fosters communication among developers. API
elements (e.g. the recall_score function and y_true parameter shown in Fig.
3), can be marked as “Complete”, indicating that the review of this element is
completed. This disables the annotation dropdown.

The bottom part of Fig. 3 shows the filter box, which is very useful to narrow
down a large API. For example, it can be used to show only API elements for
which an Add bounds check transformation has been suggested (Sec. VI-C), since
these warrant careful manual review. The GUI has many other features to
accelerate the transformation of large APIs, including:

  * •

The ability to merge different lists of annotations, which is useful when
multiple people edit the same API.

  * •

A batch mode to add annotations to all API elements that match the current
filter.

  * •

Keyboard shortcuts to quickly navigate the tree view or trigger operations.

The generation of the Python code for the adapters can also be triggered from
the GUI (Sec. V). The created files are packaged into a ZIP file and offered
for download.

##  VIII Dealing with Evolution of the Original Library

The original library may evolve independently of the adapters. As long as its
API stays unchanged, the adapters continue to work as expected. If elements
are added, we can generate initial adapters for them. If elements get deleted,
we can likewise delete the corresponding adapters. However, the API elements
for which we already created adapters could also change. Given that the number
of breaking changes in an API release is typically limited, they could be
processed manually, in the worst case. However, ideally, one would want to
avoid tedious specification of API transformations from scratch, since some
cannot be inferred and the ones that can be inferred require review (Sec. VI).
To achieve this (RQ 4), we propose the following approach:

  1. 1.

Record during the API editing process the API transformations applied to the
original library to create the current set of adapters. We call this set of
changes the adapter branch.

  2. 2.

Automatically derive the maintainer branch, i.e. the API transformations
applied to the original library by its maintainers, using a diffing approach
like UMLDiff [37], Diff-CatchUp [38], SemDiff [39, 40], AURA [41], and HiMa
[42].

  3. 3.

Merge the two sets of API transformations. For this, we can use well-known
approaches for automated detection and resolution of conflicts, such as the
ones gathered by Mens [43]. Manual intervention for conflicts that cannot be
resolved automatically, such as a renaming of the same API element on both
branches, can be integrated in the GUI for API editing (Sec. VII). The user
can either keep all changes of the adapter or maintainer branch, or decide on
a case-by-case basis.

#### Implementation for Python

The API transformations that were inferred or changed manually can be exported
from the GUI as a JSON file (Step 1). The implementation of the other steps of
our evolution support approach is ongoing work.

##  IX Evaluation

We evaluated the quality of the inferred transformations (Sec. IX-A), the
speed of the automated steps of the pipeline (Sec. IX-B), and the usability of
the GUI (Sec. IX-C). Where we refer to scikit-learn [44, 45], we mean version
0.24.2.

Note that the only similar approach to which we could have compared our
results, [18], provides no transformation inference at all. Usability could
not be compared either, due to the lack of a publicly accessible
implementation. It is worth noting, however, that the authors themselves
mention lack of usability as one of the main shortcomings of their approach.

###  IX-A Quality of Inferred Transformations

As shown in Table I, we inferred transformations in three categories, using
usage-based inference for the first two and text-based inference for the
third:

  * •

Reduction of API size (Remove, Replace with constant)

  * •

Parameter optionality (Make optional, Make required)

  * •

Enforcement of preconditions (Add bounds check, Replace with enum)

#### Usage-based inference

To quantify the achieved size-reduction, we classified API elements of the
popular machine learning library scikit-learn as unused, used but useless, and
useful based on 41,867 programs121212Mined from https://kaggle.com. that use
scikit-learn. The results, shown in Fig. 4, indicate that we can reduce the
number of classes, functions, and parameters by 20.7%, 47.4%, and 57.9%
respectively, if we keep only useful elements.

#### Text-based inference

We checked the correctness and completeness of the inference of
transformations for enforcing preconditions in two separate studies.
Initially, we worked with a team of seven students who helped us review the
complete API of scikit-learn. The results of this evaluation were addressed in
a subsequent Bachelor thesis that extended our text-based inference by many
cases that were not initially covered. Afterward, we repeated our evaluation,
this time on five widely-used libraries, each representing one step of a
typical DS workflow. We evaluated the precision on all inferred
transformations for the full API of each library. The recall was evaluated on
a random sample of 200 functions from each library, since evaluating all five
libraries in full would have exceeded our resources. This led to the results
reported in Table II and Table III.

Figure 4: Number of useful, useless and unused elements in the public API of scikit-learn. For classes and functions, all used elements are useful.  TABLE II: Precision of Text-Based Inference. The numbers in the table refer to the full library. Library | Enum | Boundary | Dependency
---|---|---|---
| TP | FP | Prec | TP | FP | Prec | TP | FP | Prec
scikit-learn | 543 | 45 | 92% | 234 | 32 | 88% | 224 | 20 | 92%
Matplotlib | 155 | 18 | 90% | 14 | 12 | 54% | 1 | 1 | 50%
Numpy | 95 | 47 | 67% | 26 | 21 | 55% | 6 | 11 | 35%
Pandas | 293 | 30 | 91% | 10 | 14 | 42% | 21 | 18 | 54%
Seaborn | 16 | 2 | 89% | 8 | 0 | 100% | 1 | 0 | 100%
TABLE III: Recall of Text-Based Inference, evaluated on a random sample of 200 functions of each library. The numbers in the table refer to these samples. Library | Enum | Boundary | Dependency
---|---|---|---
| TP | FN | Rec | TP | FN | Rec | TP | FN | Rec
scikit-learn | 34 | 2 | 94% | 25 | 7 | 78% | 21 | 19 | 53%
Matplotlib | 4 | 1 | 80% | 0 | 0 | - | 0 | 0 | -
Numpy | 7 | 6 | 54% | 0 | 1 | 0% | 1 | 1 | 50%
Pandas | 14 | 4 | 78% | 0 | 0 | - | 4 | 5 | 44%
Seaborn | 6 | 0 | 100% | 4 | 1 | 80% | 0 | 0 | -

We counted a transformation as a true positive (TP) if it did not need
changes, as a false positive (FP) if it had to be changed or removed, and as a
false negative (FN) if it was missing. The results illustrate that the Replace
with enum transformation was relevant to all libraries and that inference
worked well, except for Numpy. The FPs and FNs occurred when legal values were
only contained in the textual description, rather than the type section of a
parameter’s documentation. Better matching rules can eliminate this issue. Add
bounds check and Bundle dependent parameter (Sec. VI-C) were mostly relevant
to scikit-learn. There, the inference achieved high precision of 88% and 92%
respectively. The FPs were mostly cases where a boundary or dependency was
relativized by the phrases ‘often’, ‘typically’, or ‘usually’, which are
currently disregarded by the matching rules. The recall is at 78% and 53%
respectively. The FNs are caused by slightly different wordings than expected
by our text processing rules. These need to be generalized for better recall.

###  IX-B Speed of Automated Operations

On a test PC with Microsoft Windows 11 Pro (Build 22000), an AMD Ryzen 9 3900X
processor, Samsung SSD 960 EVO 1TB drive, and 32 GB of main memory, the
automated steps performed as follows:

  * •

The static analysis of _client code_ (Sec. VI-A and VI-B) needed 28 min 19s to
check 92,402 Python programs previously mined from Kaggle, of which 41,867
used scikit-learn. This step needs to run only once when new client code is
acquired.

  * •

The static analysis of _library code_ (Sec. V), including analysis of
_docstrings_ (Sec. VI-C), took 110.3s (mean across 10 runs) to analyze scikit-
learn. The library contains 422 classes, 2962 global functions or methods, and
8305 parameters. This includes API elements that are marked as internal via
naming convention. We include them into our analysis since Python offers no
means to hide them, so users could still use them.

  * •

The _adapter generation_ (Sec. V) was completed in 0.86s (mean across 10
runs). It produced 55 Python files with a total of 3,067 lines of code and
20,683 lines of documentation. We determined this with
cloc131313https://github.com/AlDanial/cloc.

We conclude that the efficiency of the automated steps is very good. For each
new release of a library, our tool needs below two minutes for analyzing its
complete code, inferring sensible improvements, and generating the
corresponding adapters. The main limiting factor is the one-time collection of
client programs for our usage analysis. This was slow because of the rate-
limited API of Kaggle. Other interesting sources, e.g. GitHub, have similar
intentional limitations.

###  IX-C Usability of GUI

We evaluated the usability of the API Editor in a study with 5 bachelor
students, who were familiar with Python, refactoring and design patterns but
had no prior knowledge of the API Editor. Since no public prototype of the
only closely related approach [18] is available, we compared the automatic
adapter generation via our API Editor (version 1.64.1) to what we consider the
best state-of-the-art tool for (partly) automated creation of adapters: Use of
GitHub copilot141414https://github.com/features/copilot (version 1.1.29.1869)
in PyCharm (version 222.3345.131). GitHub copilot excels at suggesting code
that obeys strict patterns, like adapters. PyCharm offers the most
comprehensive support for Python programming, in our opinion.

Our study compared these two approaches by creating adapters for Matplotlib
v3.5.3 [46]. This is a popular API for plotting, a domain that can be
understood intuitively.

To limit fatigue, the study consisted of four main blocks, each constrained to
30 minutes, with two 15-minute breaks in-between. The session was recorded
with permission and participants were asked to think aloud [47, 48, 49, 50,
51, 52] while working on the tasks.

In the first block, we introduced 10 general usability guidelines based on
[20], to give participants an idea about good API design without biasing them
towards the changes that can be made with the API Editor.

In the second block, the participants were asked to find usability issues in
an area of Matplotlib of their choice, based on its API
documentation151515https://matplotlib.org/stable/api/index.html and the source
code linked from there. Participants could creatively come up with their own
usability and design flaws. This was again intended to prevent potential bias
towards our tool. The participants wrote down each issue they found, including
its precise location in the API and a possible resolution.

In the third block, we briefly explained the transformations supported by the
API Editor and provided as reference a specification similar to Table I. The
participants then used the API Editor to resolve the issues they had found in
the previous step. Once they were done, they filled out a standard System
Usability Scale (SUS) questionnaire ([53, 54, 55, 56]) about their experience
of using the API Editor on the Matplotlib API.

In the final block, we reminded participants of the adapter design pattern and
explained its implementation in Python for global functions and instance
methods of classes. The participants were again provided with a reference.
Then, they were asked to implement adapters with GitHub Copilot in PyCharm,
for the changes that they had previously performed with the API Editor.
Finally, the participants filled out a SUS questionnaire about the usability
of GitHub Copilot and PyCharm for improving the Matplotlib API.

The API Editor received scores between 82.5 and 90 with a median of 90.
Everything above 85.5 corresponds to excellent usability on the SUS scale. The
scores regarding the use of PyCharm for this task varied between 22.5 and 70
with a median of 55. The lowest score, however, was given by a person without
prior experience with PyCharm, so we regard it as an outlier. This lifts the
median to 58.75. Still, the highest score, 70, is just slightly higher than
the SUS ‘above average’ mark of 68. Thus, the API Editor provided a
significantly improved user experience.

We also counted how often participants succeeded or failed in implementing
fixes for the design flaws that they had found in Matplotlib. Then we compared
the results for the API Editor and GitHub Copilot. All renames (41), removals
(12), and additions of bounds checks (2) were performed correctly in the API
Editor. The attempts to combine two functions (3) or split a function into two
(3) could not be done in the API Editor, since they are not generally
adaptorable. No participant managed to implement all the adapters correctly
with PyCharm, though. 14 of 41 attempts to rename a function using an adapter
were implemented wrongly (often, multiple issues affected the same adapter).
Common mistakes included:

  * •

Incorrect parameter list of an adapter (9 cases).

  * •

Passing a keyword-only argument by position (9 cases).

  * •

Passing the original default value in the call to the original library,
instead of the value of the corresponding parameter of the adapter (3 cases).

Experienced Python programmers might not make these mistakes. Still, the
evaluation clearly showed that the API Editor is valuable, especially for
novices, to prevent errors caused by uncommon language features.

###  IX-D Threats to Validity

#### Participants of usability study

All participants of the usability study (Sec. IX-C) were bachelor students.
More experienced developers would be able to implement the adapters with fewer
bugs, which we could also see in our small target group. Likewise, better
training would improve the performance. But even then, bugs would be
inevitable when creating adapters for all elements of a large library.
Moreover, training developers or employing more senior ones would increase
cost. Finally, adapter creation is a rather boring task that would negatively
impact the happiness of developers, particularly senior ones.

#### Design of usability study

Instead of finding usability issues by writing own code that _uses_
Matplotlib, participants of the usability study (Sec. IX-C) were asked to find
flaws just by looking at the documentation and library code, due to time
constraints. This way, participants might have only detected simple usability
problems and missed more in-depth issues [57] for which no annotation exists
yet in the API Editor. Moreover, even though all tasks were constrained to 30
minutes with breaks in between, fatigue might have had a negative impact on
the SUS scores of the second experiment (manual adapter creation via PyCharm
and GitHub Copilot). However, we do not expect the large gap between the SUS
scores of the two different approaches to vanish completely.

#### Applicability to other libraries

Our findings regarding the usability of the GUI (Sec. IX-C) can be generalized
to any library, since the GUI works the same for them all. Likewise, we expect
the performance of the automated parts of the pipeline (Sec. IX-B) to be
sufficient, since scikit-learn is already fairly large. The quality of
inferred transformations (Sec. IX-A) depends on the existence of high-quality
docstrings in the library code and the availability of large amounts of client
code, though.

##  X Related Work

#### API complexity

Removing unused and useless API elements follows the API usability principle
of “aesthetic and minimalist design” [20]. The empirical study [58] suggests
that having more API elements inside the same namespace reduces the success
rate of finding the ones needed for a given task. Stylos and Myers found that
this effect could be mitigated by proper naming and particularly distinct name
prefixes to make full use of auto-completion [59]. Unnecessary API complexity
was also the second most common issue identified in the Contextual Interaction
Framework during a heuristic evaluation [57]. Lämmel et al. [60] analyzed
usage patterns of Java APIs that are useful for API migration.

Several guidelines exist regarding the number of parameters a function should
have: Martin [61, p. 288] suggests avoiding more than three parameters.
McConnell’s guideline [62, p. 178] is to use at most seven parameters, based
on psychological research. Stylos and Clarke [63] even found parameterless
constructors with subsequent setters to offer better usability than
constructors with parameters. Complexity is also a recurring theme in
usability metrics for APIs [1, 64, 58].

Debloating [65, 66] offers a way to produce a reduced program that still works
correctly for existing clients on a given set of inputs. Adaptoring,
meanwhile, creates a new, reduced API, designed to improve learnability and
reduce the risk of misuse by new clients. It does not reduce the memory
footprint of the program but increases it, adding the adapter code to the code
of the original library, which must still be installed.

#### API usability

General guidelines for high-quality code like [61, 62, 6] apply especially to
public APIs. Additionally, Bloch [67] as well as Myers and Stylos [20] provide
specific guidance for API design. Many more studies exist about this topic:
Hou and Li [68] investigated discussions from the Java Swing forum to find
common learning hurdles. Grill et al. [57] evaluated the usability of an API
using various methods from the area of human computer interaction. Scheller
and Kuhn [21] compared two API designs in a study to determine their usability
when instantiating classes or calling methods. Murphy et al. [69] aimed to
better understand the process behind API design through developer interviews.

#### Code smells

Given the similarity of adaptorable API transformations and refactorings,
inference of recommendable API transformations is akin to the detection of
code smells [6], which indicate places where refactorings should be applied.
For example, a field with a Mysterious Name is an opportunity to apply the
Rename Field refactoring. Lacerda et al. [70] reviewed literature about the
definition of code smells, means to detect them, and their correspondence to
refactoring. They also found that code smells and refactorings are strongly
linked to code quality dimensions, e.g. understandability. Santos et al. [71],
however, concluded that no strong correlation between code smells and code
quality exists, and that developers rarely agree with detected code smells.
Yamashita and Moonen [72] found that developers with higher knowledge about
code smells also found them more important. Fontana et al. [73], Al Dallal
[74], Rasool and Arshad [75], and Di Nucci et al. [76] surveyed approaches to
detect code smells. This field of research is too wide to fully do it justice
here.

##  XI Limitations and Future Work

#### Static analysis of Python code

We opted to use purely _static_ analysis of the library and client code, so
users would not accidentally execute arbitrary code pulled from the internet
on their machine.

Because of this, our prototype implementation cannot accurately capture the
entire API of every Python library. For instance, Python’s decorators (PEP 318
[77]) can be used to alter the signature of a function at runtime. This can be
overcome by using the inspect module [78], which offers introspection at
runtime and can capture dynamic signature modifications. The inspect module
can be used together with library code without additional security risks,
since library code must run on the user’s machine anyway when they use the
generated adapters. Since library developers are generally careful not to
execute slow code when a module gets imported, the performance of the analysis
should still suffice.

Another challenge pose dynamic imports [79], which make it statically
impossible to determine all call targets in client code. This limits the
quality of our usage analysis and can only be overcome by fully dynamic
analysis. However, measures must be taken for sandboxing of analyzed client
code. Also, the greatly increased runtime of the analysis must be considered,
e.g. if client code is interspersed with the training of machine learning
models.

#### Inference of API transformations

The usage-based analysis (Sec. VI-A and VI-B) needs representative client
code. The API designer is responsible for collecting it from general-purpose
sources, such as GitHub, or domain-specific sources like Kaggle. They must
also set the threshold and significance level appropriately, depending on the
available client code. However, unless we analyze all client code, which is
impossible for open-source libraries, the absence of usages of a function in
the analyzed client code does not imply that the function is not used in
general. As Hyrum’s Law [80] suggests, with enough users, all API elements
will eventually be used. Moreover, we cannot always derive the target of a
call, as explained above. Therefore, we might incorrectly mark a function as
unused that is used in the analyzed client code.

Additionally, we don’t consider _when_ a function was added to a library: A
more recent addition is likely to be used less than a function that has been
part of the library for years. We can similarly discuss the correctness of
other automated suggestions. The main takeaway here is that, ultimately, the
user of the tool is responsible for the redesign of the API. The automated
suggestions are meant to accelerate the redesign process, but must be reviewed
carefully.

#### Applicability to other languages

Our prototype implementation can only handle Python libraries. This, however,
is not a limitation of the adaptoring approach. On the contrary, it would be
easier to handle a language like Java, whose enforced static typing eases
thorough static analysis of library and client code, and whose standardized,
rich documentation format, greatly simplifies text-based analysis.

#### Additional API transformations

We only implemented a subset of the adaptorable API transformations (Sec. IV)
to test the different elements of our approach, including the analysis of
library code, the contained docstrings, and client code. Additional
transformations can be added later. But, as with refactorings, a
transformation must be needed often enough to justify the effort to automate
it. Thus, there will always be cases where manual changes are required.

#### Manual changes to adapters

Currently, we don’t offer a way to edit the generated adapters and preserve
the edits when adapters are re-generated. To support this, manually adjusted
adapters must be detected and no longer overridden. This entails that the
previous adapters become an input to the adapter generation process.

##  XII Conclusion

Adaptoring can provide a new API for an existing library to fix learnability
and usability issues or support different client profiles. The new API is
realized as a wrapper library that calls the original library. Because the
original library remains untouched, adaptoring avoids breaking existing
clients and the need to keep deprecated API elements. Because no code is
duplicated, adaptoring can leverage bug fixes or performance improvements to
the original library. Most API-transforming refactorings can be realized by
adaptoring (Sec. IV, RQ 1).

To make adaptoring viable for large libraries, many tasks are automated: Given
a list of API transformations, adapter code gets generated automatically (Sec.
V). Unlike in [18], a subset of API transformations can be inferred
automatically (RQ 2): Combined usage and documentation analysis detects
opportunities for deletion of useless API elements, making parameters optional
or mandatory, and enforcement of various preconditions. The inferred
transformations can be reviewed and edited by a human in a novel graphical
user interface, (Sec. VII, RQ 3), which received an excellent median system
usability score of 90 (Sec. IX-C).

We implemented our approach for Python and applied it to popular libraries for
data science. Our text-based inference of preconditions achieves high
precision (88 to 92%) with varying recall (53 to 94%) on scikit-learn.
Improving recall is mainly an issue of adding further text processing rules to
cover yet missed cases (Sec. IX-A). All automated steps together took less
than 31 minutes on the scikit-learn library and 41,867 client programs (Sec.
IX-B).

We also investigated how to keep the generated adapters updated automatically
in case the API of the original library changes (RQ 4) by treating the API
transformations applied by the maintainers of the original library and by the
adaptorers as two separate branches that need to be merged (Sec. VIII).
Implementation of this step is ongoing.

Overall, adaptoring allows making an improved API available to the user
community with little effort, either as the finished result or as an
intermediate step to gather feedback. If necessary, the API can then be
refined further in fast iterations.

## Acknowledgments

Nils Vollroth implemented the text-based inference of API transformations and
performed the evaluation summarized in Tables II and III. The current state of
the project would not have been possible without the numerous enthusiastic
contributors to the repository. The insightful comments of the anonymous
reviewers helped improve the final paper.

## References

  * [1] C. R. B. de Souza and D. L. M. Bentolila, “Automatic evaluation of API usability using complexity metrics and visualizations,” in _2009 31st International Conference on Software Engineering - Companion Volume_ , May 2009, pp. 299–302.
  * [2] W. Wang and M. W. Godfrey, “Detecting api usage obstacles: A study of ios and android developer questions,” in _2013 10th Working Conference on Mining Software Repositories (MSR)_ , 2013, pp. 61–64.
  * [3] M. Zibran, F. Eishita, and C. Roy, “Useful, but usable? factors affecting the usability of apis,” in _2011 18th Working Conference on Reverse Engineering_ , 10 2011, pp. 151–155.
  * [4] M. P. Robillard, “What makes apis hard to learn? answers from developers,” _IEEE Software_ , vol. 26, no. 6, pp. 27–34, 2009.
  * [5] M. P. Robillard and R. DeLine, “A field study of api learning obstacles,” _Empirical Software Engineering_ , vol. 16, p. 703–732, 2011.
  * [6] M. Fowler, _Refactoring_ , 2nd ed., ser. Addison-Wesley Signature Series (Fowler). Boston, MA: Addison-Wesley, 2018\.
  * [7] D. Dig and R. Johnson, “The role of refactorings in API evolution,” in _21st IEEE International Conference on Software Maintenance (ICSM’05)_ , Sep. 2005, pp. 389–398.
  * [8] R. Koçi, X. Franch, P. Jovanovic, and A. Abelló, “Classification of Changes in API Evolution,” in _2019 IEEE 23rd International Enterprise Distributed Object Computing Conference (EDOC)_ , Oct. 2019, pp. 243–249.
  * [9] T. Mens and T. Tourwe, “A survey of software refactoring,” vol. 30, no. 2, pp. 126–139, 2004.
  * [10] M. Abebe and C.-J. Yoo, “Trends, Opportunities and Challenges of Software Refactoring: A Systematic Literature Review,” 2014.
  * [11] A. A. B. Baqais and M. Alshayeb, “Automatic software refactoring: A systematic literature review,” vol. 28, no. 2, pp. 459–502, 2020. [Online]. Available: https://doi.org/10.1007/s11219-019-09477-y
  * [12] J. H. Perkins, “Automatically generating refactorings to support API evolution,” in _Proceedings of the 6th ACM SIGPLAN-SIGSOFT Workshop on Program Analysis for Software Tools and Engineering_ , ser. PASTE ’05. New York, NY, USA: Association for Computing Machinery, Sep. 2005, pp. 111–114.
  * [13] J. Henkel and A. Diwan, “CatchUp! capturing and replaying refactorings to support API evolution,” in _Proceedings of the 27th International Conference on Software Engineering_ , ser. ICSE ’05. New York, NY, USA: Association for Computing Machinery, May 2005, pp. 274–283.
  * [14] A. Hora, A. Etien, N. Anquetil, S. Ducasse, and M. T. Valente, “APIEvolutionMiner: Keeping API evolution under control,” in _2014 Software Evolution Week - IEEE Conference on Software Maintenance, Reengineering, and Reverse Engineering (CSMR-WCRE)_ , Feb. 2014, pp. 420–424.
  * [15] Z. Xing and E. Stroulia, “API-Evolution Support with Diff-CatchUp,” _IEEE Transactions on Software Engineering_ , vol. 33, no. 12, pp. 818–836, Dec. 2007.
  * [16] M. Lamothe and W. Shang, “Exploring the use of automated api migrating techniques in practice: An experience report on android,” _2018 IEEE/ACM 15th International Conference on Mining Software Repositories (MSR)_ , pp. 503–514, 2018. [Online]. Available: https://api.semanticscholar.org/CorpusID:50775620
  * [17] C. H. Kao, C.-Y. Chang, and H. C. Jiau, “Towards cost-effective api deprecation: A win–win strategy for api developers and api users,” _Information and Software Technology_ , vol. 142, p. 106746, 2022. [Online]. Available: https://www.sciencedirect.com/science/article/pii/S0950584921001944
  * [18] U. Jugel, “Generating Smart Wrapper Libraries for Arbitrary APIs,” in _Software Language Engineering_ , ser. Lecture Notes in Computer Science, M. van den Brand, D. Gašević, and J. Gray, Eds. Springer, 2010, pp. 354–373.
  * [19] E. Gamma, R. Helm, R. Johnson, and J. M. Vlissides, _Design Patterns: Elements of Reusable Object-Oriented Software_ , 1st ed. Addison-Wesley Professional, 1994.
  * [20] B. A. Myers and J. Stylos, “Improving API usability,” _Communications of the ACM_ , vol. 59, no. 6, pp. 62–69, May 2016.
  * [21] T. Scheller and E. Kuhn, “Influencing Factors on the Usability of API Classes and Methods,” in _2012 IEEE 19th International Conference and Workshops on Engineering of Computer-Based Systems_ , Apr. 2012, pp. 232–241.
  * [22] Z. Kurbatova, I. Veselov, Y. Golubev, and T. Bryksin, “Recommendation of move method refactoring using path-based representation of code,” in _Proceedings of the IEEE/ACM 42nd International Conference on Software Engineering Workshops_ , ser. ICSEW’20. New York, NY, USA: Association for Computing Machinery, 2020, p. 315–322. [Online]. Available: https://doi.org/10.1145/3387940.3392191
  * [23] R. Terra, M. T. Valente, S. Miranda, and V. Sales, “Jmove: A novel heuristic and tool to detect move method refactoring opportunities,” _Journal of Systems and Software_ , vol. 138, pp. 19–36, 2018. [Online]. Available: https://www.sciencedirect.com/science/article/pii/S0164121217302960
  * [24] C. M. S. Couto, H. Rocha, and R. Terra, “A quality-oriented approach to recommend move method refactorings,” in _Proceedings of the XVII Brazilian Symposium on Software Quality_ , ser. SBQS ’18. New York, NY, USA: Association for Computing Machinery, 2018, p. 11–20. [Online]. Available: https://doi.org/10.1145/3275245.3275247
  * [25] G. Bavota, R. Oliveto, M. Gethers, D. Poshyvanyk, and A. De Lucia, “Methodbook: Recommending move method refactorings via relational topic models,” _IEEE Transactions on Software Engineering_ , vol. 40, no. 7, pp. 671–694, 2014.
  * [26] V. Sales, R. Terra, L. F. Miranda, and M. T. Valente, “Recommending move method refactorings using dependency sets,” in _2013 20th Working Conference on Reverse Engineering (WCRE)_ , 2013, pp. 232–241.
  * [27] T. Xu, L. Jin, X. Fan, Y. Zhou, S. Pasupathy, and R. Talwadker, “Hey, you have given me too many knobs!: Understanding and dealing with over-designed configuration in system software,” in _Proceedings of the 2015 10th Joint Meeting on Foundations of Software Engineering_ , ser. ESEC/FSE 2015. New York, NY, USA: Association for Computing Machinery, Aug. 2015, pp. 307–319.
  * [28] T. Gowers, J. Barrow-Green, and I. Leader, Eds., _The Princeton Companion to Mathematics_. Princeton: Princeton University Press, 2008.
  * [29] Wikipedia contributors, “P-value — Wikipedia, the free encyclopedia,” 2022, [Online; accessed 31-August-2022]. [Online]. Available: https://en.wikipedia.org/w/index.php?title=P-value&oldid=1102157223
  * [30] T. Freeman and F. Pfenning, “Refinement types for ml,” _SIGPLAN Not._ , vol. 26, no. 6, p. 268–277, may 1991. [Online]. Available: https://doi.org/10.1145/113446.113468
  * [31] reStructuredText Primer — Sphinx documentation. Sphinx. [Online]. Available: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
  * [32] Style guide — numpydoc v1.6.0rc2.dev0 Manual. Numpy. [Online]. Available: https://numpydoc.readthedocs.io/en/latest/format.html
  * [33] Styleguide. Google. [Online]. Available: https://google.github.io/styleguide/pyguide.html
  * [34] The Epytext Markup Language. Epydoc. [Online]. Available: https://epydoc.sourceforge.net/epytext.html
  * [35] M. Honnibal, I. Montani, S. Van Landeghem, and A. Boyd, “spaCy: Industrial-strength Natural Language Processing in Python,” 2020.
  * [36] X. Wang and L. Zhao, “Apicad: Augmenting api misuse detection through specifications from code and documents,” in _2023 IEEE/ACM 45th International Conference on Software Engineering (ICSE)_ , 2023, pp. 245–256.
  * [37] Z. Xing and E. Stroulia, “UMLDiff: An algorithm for object-oriented design differencing,” in _Proceedings of the 20th IEEE/ACM International Conference on Automated Software Engineering - ASE ’05_. ACM Press, p. 54. [Online]. Available: http://portal.acm.org/citation.cfm?doid=1101908.1101919
  * [38] ——, “API-Evolution Support with Diff-CatchUp,” _IEEE Transactions on Software Engineering_ , vol. 33, no. 12, pp. 818–836, Dec. 2007. [Online]. Available: http://ieeexplore.ieee.org/document/4359473/
  * [39] B. Dagenais and M. P. Robillard, “Recommending adaptive changes for framework evolution,” in _Proceedings of the 30th International Conference on Software Engineering_ , ser. ICSE ’08. Association for Computing Machinery, pp. 481–490. [Online]. Available: https://doi.org/10.1145/1368088.1368154
  * [40] ——, “SemDiff: Analysis and recommendation support for API evolution,” in _2009 IEEE 31st International Conference on Software Engineering_. IEEE, pp. 599–602. [Online]. Available: http://ieeexplore.ieee.org/document/5070565/
  * [41] W. Wu, Y.-G. Guéhéneuc, G. Antoniol, and M. Kim, “AURA: A hybrid approach to identify framework evolution,” in _Proceedings of the 32nd ACM/IEEE International Conference on Software Engineering - Volume 1_. ACM, pp. 325–334. [Online]. Available: https://dl.acm.org/doi/10.1145/1806799.1806848
  * [42] S. Meng, X. Wang, L. Zhang, and H. Mei, “A history-based matching approach to identification of framework evolution,” in _2012 34th International Conference on Software Engineering (ICSE)_. IEEE, pp. 353–363. [Online]. Available: http://ieeexplore.ieee.org/document/6227179/
  * [43] T. Mens, “A state-of-the-art survey on software merging,” _IEEE Transactions on Software Engineering_ , vol. 28, no. 5, pp. 449–462, 2002.
  * [44] L. Buitinck, G. Louppe, M. Blondel, F. Pedregosa, A. Mueller, O. Grisel, V. Niculae, P. Prettenhofer, A. Gramfort, J. Grobler, R. Layton, J. VanderPlas, A. Joly, B. Holt, and G. Varoquaux, “API design for machine learning software: experiences from the scikit-learn project,” in _ECML PKDD Workshop: Languages for Data Mining and Machine Learning_ , 2013, pp. 108–122.
  * [45] F. Pedregosa, G. Varoquaux, A. Gramfort, V. Michel, B. Thirion, O. Grisel, M. Blondel, P. Prettenhofer, R. Weiss, V. Dubourg, J. Vanderplas, A. Passos, D. Cournapeau, M. Brucher, M. Perrot, and E. Duchesnay, “Scikit-learn: Machine learning in Python,” _Journal of Machine Learning Research_ , vol. 12, pp. 2825–2830, 2011.
  * [46] J. D. Hunter, “Matplotlib: A 2d graphics environment,” _Computing in Science & Engineering_, vol. 9, no. 3, pp. 90–95, 2007.
  * [47] K. A. Ericsson and H. A. Simon, _Protocol Analysis: Verbal Reports as Data_ , ser. Protocol Analysis: Verbal Reports as Data. Cambridge, MA, US: The MIT Press, 1984.
  * [48] T. Boren and J. Ramey, “Thinking aloud: Reconciling theory and practice,” _IEEE Transactions on Professional Communication_ , vol. 43, no. 3, pp. 261–278, Sep. 2000.
  * [49] E. Charters, “The Use of Think-aloud Methods in Qualitative Research An Introduction to Think-aloud Methods,” _Brock Education Journal_ , vol. 12, no. 2, Jul. 2003.
  * [50] M. W. M. Jaspers, T. Steen, C. van den Bos, and M. Geenen, “The think aloud method: A guide to user interface design,” _International Journal of Medical Informatics_ , vol. 73, no. 11, pp. 781–795, Nov. 2004.
  * [51] K. A. Young, “Direct from the source: The value of ’think-aloud’ data in understanding learning,” _Journal of Educational Enquiry_ , vol. 6, p. 10, 2009.
  * [52] D. W. Eccles and G. Arsal, “The think aloud method: What is it and how do I use it?” _Qualitative Research in Sport, Exercise and Health_ , vol. 9, no. 4, pp. 514–531, Aug. 2017.
  * [53] J. Brooke, “SUS - A quick and dirty usability scale,” _Usability Eval. Ind._ , vol. 189, p. 7, 1995.
  * [54] A. Bangor, P. T. Kortum, and J. T. Miller, “An Empirical Evaluation of the System Usability Scale,” _International Journal of Human–Computer Interaction_ , vol. 24, no. 6, pp. 574–594, Jul. 2008.
  * [55] S. Mclellan, A. Muddimer, and S. Peres, “The Effect of Experience on System Usability Scale Ratings,” _Journal of Usability Studies_ , vol. 7, Nov. 2011.
  * [56] J. R. Lewis, “The System Usability Scale: Past, Present, and Future,” _International Journal of Human–Computer Interaction_ , vol. 34, no. 7, pp. 577–590, Jul. 2018.
  * [57] T. Grill, O. Polacek, and M. Tscheligi, “Methods towards api usability: A structural analysis of usability problem categories,” in _Human-Centered Software Engineering_ , M. Winckler, P. Forbrig, and R. Bernhaupt, Eds. Berlin, Heidelberg: Springer Berlin Heidelberg, 2012, pp. 164–180.
  * [58] T. Scheller and E. Kühn, “Automated measurement of API usability,” _Information and Software Technology_ , vol. 61, no. C, pp. 145–162, May 2015\.
  * [59] J. Stylos and B. A. Myers, “The implications of method placement on API learnability,” in _Proceedings of the 16th ACM SIGSOFT International Symposium on Foundations of Software Engineering_ , ser. SIGSOFT ’08/FSE-16. New York, NY, USA: Association for Computing Machinery, Nov. 2008, pp. 105–112.
  * [60] R. Lämmel, E. Pek, and J. Starek, “Large-scale, AST-based API-usage analysis of open-source Java projects,” in _Proceedings of the 2011 ACM Symposium on Applied Computing_ , ser. SAC ’11. Association for Computing Machinery, 2011, pp. 1317–1324. [Online]. Available: https://dl.acm.org/doi/10.1145/1982185.1982471
  * [61] R. C. Martin, _Clean Code: A Handbook of Agile Software Craftsmanship_ , 1st ed. Prentice Hall, 2009.
  * [62] S. McConnell, _Code Complete_ , 2nd ed. Microsoft Press, 2004.
  * [63] J. Stylos and S. Clarke, “Usability Implications of Requiring Parameters in Objects’ Constructors,” in _Proceedings of the 29th International Conference on Software Engineering_ , ser. ICSE ’07. USA: IEEE Computer Society, May 2007, pp. 529–539.
  * [64] G. M. Rama and A. Kak, “Some structural measures of API usability: SOME STRUCTURAL MEASURES OF API USABILITY,” _Software: Practice and Experience_ , vol. 45, no. 1, pp. 75–110, Jan. 2015.
  * [65] Q. Xin, M. Kim, Q. Zhang, and A. Orso, “Subdomain-based generality-aware debloating,” in _2020 35th IEEE/ACM International Conference on Automated Software Engineering (ASE)_ , 2020, pp. 224–236.
  * [66] Q. Xin, Q. Zhang, and A. Orso, “Studying and understanding the tradeoffs between generality and reduction in software debloating,” in _Proceedings of the 37th IEEE/ACM International Conference on Automated Software Engineering_ , ser. ASE ’22. New York, NY, USA: Association for Computing Machinery, 2023. [Online]. Available: https://doi.org/10.1145/3551349.3556970
  * [67] J. Bloch, “How to design a good API and why it matters,” in _Companion to the 21st ACM SIGPLAN Symposium on Object-oriented Programming Systems, Languages, and Applications_ , ser. OOPSLA ’06. New York, NY, USA: Association for Computing Machinery, Oct. 2006, pp. 506–507.
  * [68] D. Hou and L. Li, “Obstacles in Using Frameworks and APIs: An Exploratory Study of Programmers’ Newsgroup Discussions,” in _2011 IEEE 19th International Conference on Program Comprehension_ , Jun. 2011, pp. 91–100.
  * [69] L. Murphy, M. B. Kery, O. Alliyu, A. Macvean, and B. A. Myers, “API Designers in the Field: Design Practices and Challenges for Creating Usable APIs,” in _2018 IEEE Symposium on Visual Languages and Human-Centric Computing (VL/HCC)_ , Oct. 2018, pp. 249–258.
  * [70] G. Lacerda, F. Petrillo, M. Pimenta, and Y. G. Guéhéneuc, “Code smells and refactoring: A tertiary systematic review of challenges and observations,” vol. 167, p. 110610, 2020. [Online]. Available: https://www.sciencedirect.com/science/article/pii/S0164121220300881
  * [71] J. A. M. Santos, J. B. Rocha-Junior, L. C. L. Prates, R. S. do Nascimento, M. F. Freitas, and M. G. de Mendonça, “A systematic review on the code smell effect,” vol. 144, pp. 450–477, 2018. [Online]. Available: https://www.sciencedirect.com/science/article/pii/S0164121218301444
  * [72] A. Yamashita and L. Moonen, “Do developers care about code smells? An exploratory survey,” in _2013 20th Working Conference on Reverse Engineering (WCRE)_ , 2013, pp. 242–251.
  * [73] F. Arcelli Fontana, P. Braione, and M. Zanoni, “Automatic detection of bad smells in code: An experimental assessment,” vol. 11, 2012.
  * [74] J. Al Dallal, “Identifying refactoring opportunities in object-oriented code: A systematic literature review,” vol. 58, pp. 231–249, 2015. [Online]. Available: https://www.sciencedirect.com/science/article/pii/S0950584914001918
  * [75] G. Rasool and Z. Arshad, “A review of code smell mining techniques,” vol. 27, no. 11, pp. 867–895, 2015. [Online]. Available: https://onlinelibrary.wiley.com/doi/abs/10.1002/smr.1737
  * [76] D. Di Nucci, F. Palomba, D. A. Tamburri, A. Serebrenik, and A. De Lucia, “Detecting code smells using machine learning techniques: Are we there yet?” in _2018 IEEE 25th International Conference on Software Analysis, Evolution and Reengineering (SANER)_ , 2018, pp. 612–621.
  * [77] K. D. Smith, J. J. Jewett, S. Montanaro, and A. Baxter. (2003) PEP 318 – Decorators for Functions and Methods — peps.python.org. [Online]. Available: https://peps.python.org/pep-0318/
  * [78] Inspect — Inspect live objects. Python documentation. [Online]. Available: https://docs.python.org/3/library/inspect.html
  * [79] Importlib — The implementation of import. Python documentation. [Online]. Available: https://docs.python.org/3/library/importlib.html
  * [80] H. Wright. Hyrum’s Law. [Online]. Available: https://www.hyrumslaw.com/