-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathmaintenance_evolution.Rmd
More file actions
251 lines (176 loc) · 11.1 KB
/
Copy pathmaintenance_evolution.Rmd
File metadata and controls
251 lines (176 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
---
aliases:
- evolution.html
---
# Package evolution - changing stuff in your package {#evolution}
```{block, type="summaryblock"}
This chapter presents our guidance for changing stuff in your package: changing parameter names, changing function names, deprecating functions, and even retiring and archiving packages.
This chapter was initially contributed as a tech note on rOpenSci website by [Scott Chamberlain](https://github.com/sckott); you can read the [original version](https://ropensci.org/technotes/2017/01/05/package-evolution/)._
```
## Philosophy of changes {#philosophy-of-changes}
Everyone's free to have their own opinion about how freely parameters/functions/etc. are changed in a library - rules about package changes are not enforced by CRAN or otherwise. Generally, as a library gets more mature, changes to user-facing methods (i.e., exported functions in an R package) should become very rare. Libraries that are dependencies of many other libraries are likely to be more careful about changes, and should be.
## The lifecycle package {#the-lifecycle-package}
This chapter presents solutions that do not require the lifecycle package but you might still find it useful.
We recommend [reading the lifecycle documentation](https://lifecycle.r-lib.org/articles/stages.html).
## Parameters: changing parameter names {#parameters-changing-parameter-names}
Sometimes parameter names must be changed for clarity, or for some other reason.
A possible approach is to check if deprecated arguments are not missing, and stop providing a meaningful message.
```r
foo_bar <- function(x, y) {
if (!missing(x)) {
stop("use 'y' instead of 'x'")
}
y^2
}
foo_bar(x = 5)
#> Error in foo_bar(x = 5) : use 'y' instead of 'x'
```
If you want the function to be more helpful, you could change it to emit a warning but automatically take the necessary action:
```r
foo_bar <- function(x, y) {
if (!missing(x)) {
warning("use 'y' instead of 'x'")
y <- x
}
y^2
}
foo_bar(x = 5)
#> 25
```
Be aware of the parameter `...`. If your function has `...`, and you have already removed a parameter (let's call it `z`), a user may have older code that uses `z`. When they pass in `z`, it's not a parameter in the function definition, and will likely be silently ignored -- not what you want. Instead, leave the argument around, throwing an error if it used.
## Functions: changing function names {#functions-changing-function-names}
If you must change a function name, do it gradually, as with any other change in your package.
Let's say you have a function `foo`.
```r
foo <- function(x) x + 1
```
However, you want to change the function name to `bar`.
Instead of simply changing the function name and `foo` no longer existing straight away, in the first version of the package where `bar` appears, make an alias like:
```r
#' foo - add 1 to an input
#' @export
foo <- function(x) x + 1
#' @export
#' @rdname foo
bar <- foo
```
With the above solution, the user can use either `foo()` or `bar()` -- either will do the same thing, as they are the same function.
It's also useful to have a message but then you'll only want to throw that message when they use the old function, e.g.,
```r
#' foo - add 1 to an input
#' @export
foo <- function(x) {
warning("please use bar() instead of foo()", call. = FALSE)
bar(x)
}
#' @export
#' @rdname foo
bar <- function(x) x + 1
```
After users have used the package version for a while (with both `foo` and `bar`), in the next version you can remove the old function name (`foo`), and only have `bar`.
```r
#' bar - add 1 to an input
#' @export
bar <- function(x) x + 1
```
## Data: deprecate
If you need to deprecate a dataset provided by your package,
read the [solution proposed by Matthijs Berends on Stack Overflow](https://stackoverflow.com/questions/33304651/data-deprecation-in-r-package/75192818#75192818), linking to [a 3-step method in Bioconductor guidance](https://contributions.bioconductor.org/deprecation.html#deprecate-dataset).
Key is the usage of [`delayedAssign()`](https://stat.ethz.ch/R-manual/R-patched/library/base/html/delayedAssign.html) to save a promise that will serve both a warning, and the data.
## Functions: deprecate \& defunct {#functions-deprecate-defunct}
To remove a function from a package (let's say your package name is `helloworld`), you can use the following protocol:
- Mark the function as deprecated in package version `x` (e.g., `v0.2.0`)
In the function itself, use `.Deprecated()` to point to the replacement function:
```r
foo <- function() {
.Deprecated("bar")
}
```
There's options in `.Deprecated` for specifying a new function name, as well as a new package name, which makes sense when moving functions into different packages.
The message that's given by `.Deprecated` is a warning, so it can be suppressed by users with `suppressWarnings()` if desired.
Make a man page for deprecated functions like:
```r
#' Deprecated functions in helloworld
#'
#' These functions still work but will be removed (defunct) in the next version.
#'
#' \itemize{
#' \item \code{\link{foo}}: This function is deprecated, and will
#' be removed in the next version of this package.
#' }
#'
#' @name helloworld-deprecated
NULL
```
This creates a man page that users can access like ``?`helloworld-deprecated` `` and they'll see in the documentation index. Add any functions to this page as needed, and take away as a function moves to defunct (see below).
- In the next version (`v0.3.0`) you can make the function defunct (that is, completely gone from the package, except for a man page with a note about it).
In the function itself, use `.Defunct()` like:
```r
foo <- function() {
.Defunct("bar")
}
```
Note that the message in `.Defunct` is an error so that the function stops whereas `.Deprecated` uses a warning that lets the function proceed.
In addition, it's good to add `...` to all defunct functions so that if users pass in any parameters they'll get the same defunct message instead of a `unused argument` message, so like:
```r
foo <- function(...) {
.Defunct("bar")
}
```
Without `...` gives:
```r
foo(x = 5)
#> Error in foo(x = 5) : unused argument (x = 5)
```
And with `...` gives:
```r
foo(x = 5)
#> Error: 'foo' has been removed from this package
```
Make a man page for defunct functions like:
```r
#' Defunct functions in helloworld
#'
#' These functions are gone, no longer available.
#'
#' \itemize{
#' \item \code{\link{foo}}: This function is defunct.
#' }
#'
#' @name helloworld-defunct
NULL
```
This creates a man page that users can access like ``?`helloworld-defunct` `` and they'll see in the documentation index. Add any functions to this page as needed. You'll likely want to keep this man page indefinitely.
### Testing deprecated functions {#testing-deprecated-functions}
You don't have to change the tests of deprecated functions until they are made defunct.
- Consider any changes made to a deprecated function. Along with using `.Deprecated` inside the function, did you change the parameters at all in the deprecated function, or create a new function that replaces the deprecated function, etc. Those changes should be tested if any are made.
- Related to above, if the deprecated function is simply getting a name change, perhaps test that the old and new functions return identical results.
- [`suppressWarnings()` could be used](https://community.rstudio.com/t/unit-testing-of-a-deprecated-function/42837/2) to suppress the warning thrown from `.Deprecated`, but tests are not user facing, so it is not that bad if the warning is thrown in tests, and the warning could even be used as a reminder to the maintainer.
Once a function is made defunct, its tests are simply removed.
## Renaming packages {#renaming-packages}
Renaming a package that is in early development is fine.
It can be the opportunity, before review, to comply with our [naming advice](#naming-your-package).
Renaming a package that is already widely adopted and/or released on CRAN is problematic.
CRAN has a [policy](https://cran.r-project.org/web/packages/policies.html) stating that Package names on CRAN are persistent and in general it is not permitted to change a package’s name.
The package under its old name might be a dependency of packages, scripts, and feature in documentation, scientific publications, and blog posts, etc.
When radically changing the interface, starting a new package from scratch, like [httr2 which is the second generation of httr](https://www.tidyverse.org/blog/2023/11/httr2-1-0-0/); or creating editions of a package like for [testthat](https://testthat.r-lib.org/articles/third-edition.html), are better strategies. If you also maintain the old package, you can soft-deprecate it with a startup message, such as in httr.
This allows users and package authors to choose when/whether to update their codebase to the new package or edition.
If you copy code from another package, make sure to acknowledge authors of the code you reuse by listing them in DESCRIPTION with a comment that states they were authors of the original package.
[Example](https://github.com/ropensci/agroclimatico/blob/2bc820cee201e27977a8a3ffa24cf7fedefe6bbd/DESCRIPTION#L25).
## Archiving packages {#archivalguidance}
Software generally has a finite lifespan, and packages may eventually need to be archived. Archived packages are [archived](https://docs.github.com/en/repositories/archiving-a-github-repository/archiving-repositories) and moved to a dedicated GitHub organization, [ropensci-archive](https://github.com/ropensci-archive). Prior to archiving, the contents of the README file should be moved to an alternative location (such as "README-OLD.md"), and replaced with minimal contents including something like the following:
```md
# <package name>
[](https://www.repostatus.org/#unsupported)
[](https://github.com/ropensci/software-review/issues/<issue_number>)
This package has been archived. The former README is now in [README-old](<link-to-README-old>).
```
The repo status badge should be "unsupported" for formerly released packages, or "abandoned" for former concept or WIP packages, in which case the badge code above should be replaced with:
```md
[](https://www.repostatus.org/#abandoned)
```
An example of a minimal README in an archived package is in [ropensci-archive/monkeylearn](https://github.com/ropensci-archive/monkeylearn/blob/master/README.md). Once the README has been copied elsewhere and reduced to minimal form, the following steps should be followed:
- [ ] Close issues with a sentence explaining the situation and linking to this guidance.
- [ ] Archive the repository on GitHub (also under repo settings).
- [ ] Transfer the repository to [ropensci-archive](https://github.com/ropensci-archive), or request an [rOpenSci staff member](https://ropensci.org/about/#team) to transfer it (you can email `info@ropensci.org`).
Archived packages may be unarchived if authors or a new person opt to resume maintenance. For that please contact rOpenSci.