Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions 01-big-shiny.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If you are reading this page, chances are you already know what a `{shiny}` appl
The beauty of `{shiny}` [@R-shiny] is that it makes it easy for someone already familiar with R to create a small app in a matter of hours.
With small and minimal `{shiny}` apps, no knowledge of HTML (HyperText Markup Language), CSS (Cascading Style Sheets) or JavaScript is required, and you do not have to think about technical elements that usually come with web applications—for example, you do not have to think about the port the application is served on: `{shiny}` picks one for you.[^big-shiny-1]
Same goes for serving external dependencies: the application comes with its set of CSS and JavaScript dependencies that a common `{shiny}` developer does not need to worry about.
And that is probably one of the main reasons why this package has become so successful over the years—**with very little training, you can rapidly create a proof-of-concept (PoC) for a data product, showcase an algorithm, or present your results in an elegant and accessible user interfaces**.
And that is probably one of the main reasons why this package has become so successful over the years—**with very little training, you can rapidly create a proof-of-concept (PoC) for a data product, showcase an algorithm, or present your results in an elegant and accessible user interface**.

[^big-shiny-1]: Of course you can specify one if you need to, but by default the package picks one.

Expand All @@ -36,7 +36,7 @@ Ready to start engineering production-grade `{shiny}` apps?
Building a `{shiny}` application seems quite straightforward when it comes to small prototypes or proof of concepts: after a few hours of practice and documentation reading, most R developers can have a small working application.\
But things change when your application reaches "the cliff of complexity",[^big-shiny-2] i.e. that moment when the application reaches a state when it can be qualified as "complex".

[^big-shiny-2]: We borrow this term from Charity Major, as heard in *Test in Production with Charity Majors* CoRecursive #019, _Aug 31, 2018_.
[^big-shiny-2]: We borrow this term from Charity Majors, as heard in *Test in Production with Charity Majors* CoRecursive #019, _Aug 31, 2018_.

But what do we mean by complexity?
Getting a clear definition is not an easy task [^big-shiny-3] as it very much depends on who is concerned and who you are talking to.
Expand Down Expand Up @@ -73,7 +73,7 @@ One of the goals of this book is to present a methodology and toolkit that will
Customers and end users see complexity as *interface complexity*.

Interface complexity can be driven by a lot of elements, for example, the probability of making an error while using the app, the difficulty in understanding the logical progression in the app, the presence of unfamiliar behavior or terms, visual distractions, etc.
This book will also bring you strategy to help you cope with the need for simplification when it comes to designing an interface.
This book will also bring you strategies to help you cope with the need for simplification when it comes to designing an interface.

### Balancing complexities

Expand Down Expand Up @@ -119,7 +119,7 @@ But before that, let's dive into code complexity.

#### A. Codebase size {.unnumbered}

The total number of lines of code, and the number of files, can be good clue of potential complexity, but only if used as an order of magnitude (for example, a 10,000-line codebase is potentially more complex than a 100-line codebase), but should not be relied on if used strictly, even more if you try to reduce the number of lines by sacrificing code readability.
The total number of lines of code, and the number of files, can be a good clue of potential complexity, but only if used as an order of magnitude (for example, a 10,000-line codebase is potentially more complex than a 100-line codebase), but should not be relied on if used strictly, even more if you try to reduce the number of lines by sacrificing code readability.

R is very permissive when it comes to indentation and line breaks, and, unlike JavaScript or CSS, it is generally not minified.[^big-shiny-6]
In R, the number of lines of code depends on your coding style and the packages you are using. For example, the `{tidyverse}` [@tidyverse2019] style guide encourages the use of `%>%` (called "pipe"), with one function by line, producing more lines in the end code: "`%>%` should always have a space before it, and should usually be followed by a new line" ([tidyverse style guide](https://style.tidyverse.org/pipes.html){target="_blank"}).
Expand Down Expand Up @@ -161,13 +161,13 @@ Six lines of code for something that could also be written in one line.
iris[1:5, "Species"]
```

In other words, using this kind of writing style can make the codebase larger in term of lines, without really adding complexity to the general program.
In other words, using this kind of writing style can make the codebase larger in terms of lines, without really adding complexity to the general program.

Another drawback of this metric is that it focuses on numbers instead of readability, and in the long run, yes, readability matters.
As noted in *The Art of Unix Programming*, "Pressure to keep the codebase size down by using extremely dense and complicated implementation techniques can cause a cascade of implementation complexity in the system, leading to an un-debuggable mess" [@ericraymond2003].

Still, this metric can be useful to reinforce what you have learned from other metrics.
It is rather unlikely that you will find this "extreme" coding style we showed above, and even if it might not make sense to compare two codebases that just differ by 1% or 2 % of lines of code, it is very likely that a codebase which is ten, one hundred, one thousand times larger is a more complex software.
It is rather unlikely that you will find this "extreme" coding style we showed above, and even if it might not make sense to compare two codebases that just differ by 1% or 2% of lines of code, it is very likely that a codebase which is ten, one hundred, one thousand times larger is more complex software.

Another good metric related to code complexity is the number of files in the project: R developers tend to split their functions into several files, so the more files you will find in a project, the larger the codebase is.
And numerous files can also be a sign of maintenance complexity, as it may be harder to reason about an app logic that is split into several files than about something that fits into one linear code inside one file.[^big-shiny-8]
Expand Down Expand Up @@ -248,7 +248,7 @@ structure(list(pkg = c("attempt", "shiny"), files = c(64L, 736L


Here, with these two metrics, we can safely assume that `{shiny}` is a more complex package than `{attempt}`.
If you want to compute the same prefix for a local package/repository, the `cloc_pkg()` function can be used.
If you want to compute the same metric for a local package/repository, the `cloc_pkg()` function can be used.
For example, here is how to compute the cloc metric for the `{hexmake}` application:

```{r 01-big-shiny-8, echo = FALSE}
Expand Down Expand Up @@ -303,8 +303,8 @@ hexmake_cloc
```


One thing that this package also allows is counting the number of lines of commented code: it's usually a good sign to see that a package has comments in its codebase, as it will allow to work more safely in the future, provided that this metric doesn't reveal that large portions of the application are "commented code" (as opposed to "code comments").
For example, here we can see that `{hemake}` has `r dplyr::filter(hexmake_cloc, language == "R") %>% dplyr::pull(loc)` lines of R code, which come with `r dplyr::filter(hexmake_cloc, language == "R") %>% dplyr::pull(comment_lines)` lines of code comments.
One thing that this package also allows is counting the number of lines of commented code: it's usually a good sign to see that a package has comments in its codebase, as it will allow you to work more safely in the future, provided that this metric doesn't reveal that large portions of the application are "commented code" (as opposed to "code comments").
For example, here we can see that `{hexmake}` has `r dplyr::filter(hexmake_cloc, language == "R") %>% dplyr::pull(loc)` lines of R code, which come with `r dplyr::filter(hexmake_cloc, language == "R") %>% dplyr::pull(comment_lines)` lines of code comments.

#### B. Cyclomatic complexity {.unnumbered}

Expand All @@ -324,7 +324,7 @@ knitr::include_graphics("img/controlflow.png")

The complexity number is then computed by taking the number of nodes, subtracting the number of edges, and adding twice the number of connected components of this graph.
The algorithm is then $M = E − N + 2P$, where $M$ is the measure, $E$ the number of edges, $N$ the number of nodes and $2P$ is twice the number of connected components.
We will not go deep into this topic, as there are a lot things going on in this computation and you can find much documentation about this online.
We will not go deep into this topic, as there are a lot of things going on in this computation and you can find much documentation about this online.
Please refer to the bibliography for further readings about the theory behind this measurement.

In R, the cyclomatic complexity can be computed using the `{cyclocomp}` [@R-cyclocomp] package.
Expand All @@ -341,7 +341,7 @@ install.packages("cyclocomp")
```

The `{cyclocomp}` package comes with three main functions: `cyclocomp()`, `cyclocomp_package()`, and `cyclocomp_package_dir()`.
While developing your application, the one you will be interested in is `cyclocomp_package_dir()`: building successful shiny apps with the `{golem}` framework means you will be building your app as a package (we will get back on that later).
While developing your application, the one you will be interested in is `cyclocomp_package_dir()`: building successful shiny apps with the `{golem}` framework means you will be building your app as a package (we will get back to that later).

Here is, for example, the cyclomatic complexity of the default golem template (assuming it is located in a `golex/` subdirectory):

Expand Down Expand Up @@ -417,7 +417,7 @@ You might have heard this saying: "if you copy and paste a piece of code twice,
Indeed, splitting code into smaller pieces lowers the local cyclomatic complexity, as smaller functions have lower cyclomatic complexity.
But that is just at a local level, and it can be a suboptimal option: having a very large number of functions calling each other can make it harder to navigate through the codebase.

In the end of the day, splitting into smaller functions is not a magic solution because:
At the end of the day, splitting into smaller functions is not a magic solution because:

- the global complexity of the app is not lowered by splitting things into pieces (just local complexity) and
- The deeper the call stack, the harder it can be to debug.
Expand Down Expand Up @@ -470,7 +470,7 @@ And run the metric for `{golem}`,
```

```{r 01-big-shiny-27, cache=TRUE, warning=FALSE, eval = FALSE}
# Using this function with{golem}
# Using this function with {golem}
frame_metric("golem")
```

Expand Down Expand Up @@ -512,11 +512,11 @@ attachment::att_from_description("golex/DESCRIPTION")
```


Some important metrics to watch there are as follow:
Some important metrics to watch there are as follows:

- Test coverage: the more the better, as a large code coverage should imply that bugs are more easily caught.
- The number of downloads: a largely downloaded package will likely be less prone to bug, as it will be used by a large user base.
- Number of dependencies: the more a package has dependencies, the more likely it is that at some point it time, something in the dependency graph will break.
- The number of downloads: a largely downloaded package will likely be less prone to bugs, as it will be used by a large user base.
- Number of dependencies: the more a package has dependencies, the more likely it is that at some point in time, something in the dependency graph will break.
- Dates of first publish on CRAN, last publish, and updates: a package actively maintained is a good sign.[^big-shiny-10]

[^big-shiny-10]: Even if this is not an absolute rule, some packages haven't been updated for a long time but are still completely reliable.
Expand Down Expand Up @@ -544,7 +544,7 @@ And this is not necessarily a bad thing!
`{shiny}` apps can definitely be used to implement production-grade [^big-shiny-11] software, but production-grade software implies production-grade software engineering.
To make your project a success, you need to use tools that reduce the complexity of your app and ensure that your app is resilient to aging.

[^big-shiny-11]: By production-grade, we mean a software that can be used in a context where people use it for doing their job, and where failures or bugs have real-life consequences.
[^big-shiny-11]: By production-grade, we mean software that can be used in a context where people use it for doing their job, and where failures or bugs have real-life consequences.

In other words, production-grade `{shiny}` apps require working with a software engineering mindset, which is not always an easy task in the R world: many R developers have learned this language as a tool for doing data analysis, building models, and making statistics; not really as a tool for building software.

Expand Down Expand Up @@ -600,4 +600,4 @@ Once the app is out, it is successful if it can **exist in the long run, with al
And this, again, is hard to do without effective planning and efficient engineering.

[^big-shiny-12]: In fact, this new person might simply be you, a month from now.
And *"You'll be there in the future too, maintaining code you may have half forgotten under the press of more recent projects. When you design for the future, the sanity you save may be your own.* [@ericraymond2003].
And *"You'll be there in the future too, maintaining code you may have half forgotten under the press of more recent projects. When you design for the future, the sanity you save may be your own."* [@ericraymond2003].
10 changes: 5 additions & 5 deletions 02-planning-ahead.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The larger the codebase, the harder it is to untangle everything and make it wor
In this book, we will present a framework called `{golem}`, which is a toolbox for building production-grade `{shiny}` applications.
Even if `{golem}` is focused on production, there is no reason not to use it for your proof of concepts: starting a new `{golem}` project is relatively straightforward, and even if you do not use the advanced features, you can use it for very small apps.
The benefit of starting straight inside a `{golem}` application really outweighs the cost.
We hear a lot the question "When should I switch to `{golem}`?" The answer is simple: do not switch to `{golem}`, start with it.
We often hear the question "When should I switch to `{golem}`?" The answer is simple: do not switch to `{golem}`, start with it.
That way, you are getting ready for complexity, and if, one day, you need to turn this small app into a production app, the foundations are there.

### Develop with the KISS principle
Expand All @@ -40,15 +40,15 @@ That way, you are getting ready for complexity, and if, one day, you need to tur
>
> *KISS principle, Wikipedia article* (<https://en.wikipedia.org/wiki/KISS_principle>)

The KISS principle, as "Keep It Simple, Stupid", should drive the implementation of features in the application to allow anyone in the future, including original developers, to take over on the development.
The KISS principle, as "Keep It Simple, Stupid", should drive the implementation of features in the application to allow anyone in the future, including original developers, to take over the development.

The story behind this principle is supposed to be that Kelly Johnson, lead engineer at the Lockheed Skunk Works, gave his workers a set of very common tools and said that every airplane should be repairable with these tools, and these tools only, so that repairing an aircraft should be possible for any average engineer.

This should be a principle to keep in mind when building applications.
Indeed, large-scale `{shiny}` projects can lead to many people working on the codebase, for a long period of time.
**A large team means a variety of skills**, with some common ground in `{shiny}` development, but potentially various levels when it comes to R, web development, or production engineering.
When choosing how and what to implement, **try to make a rule to go for the simplest solution**,[^planning-ahead-1] *i.e.* the one that any common `{shiny}` developer would be able to understand and maintain.
If you go for an exotic solution or a complex technology, be sure that you are doing it for a good reason: unknown or hard-to-grasp technology reduces the chance of finding someone that will be able to maintain that piece of code in the future, and reduce the smoothness of collaboration, as "*Code you can easily comprehend elevates absolutely everyone on your team, no matter their tenure or experience level*" [@lemaire2020].
If you go for an exotic solution or a complex technology, be sure that you are doing it for a good reason: unknown or hard-to-grasp technology reduces the chance of finding someone that will be able to maintain that piece of code in the future, and reduces the smoothness of collaboration, as "*Code you can easily comprehend elevates absolutely everyone on your team, no matter their tenure or experience level*" [@lemaire2020].

[^planning-ahead-1]: Which might not be the most "elegant" solution, but production code requires pragmatism.

Expand Down Expand Up @@ -120,7 +120,7 @@ If you follow this book's workflow, this person will first create a `{golem}` pr
Once the skeleton of the app is created, this person in charge will list all the things that have to be done.
We strongly suggest that you use `Git` with a graphical interface (GitLab, GitHub, Bitbucket, etc.) as the graphical interface to help you manage the project.
These tasks are defined as issues, and will be closed during development.
These interfaces can also be used to set continuous integration.
These interfaces can also be used to set up continuous integration.

If the team follows a `git flow` (described in Chapter \@ref(version-control)), the manager will also be in charge of reviewing and accepting the pull/merge requests to the main `dev` branch if they solve the associated issues.

Expand All @@ -130,7 +130,7 @@ Do not worry if this sounds like a foreign language to you, we will get back to

**Developers will focus on small features**.
If the person in charge has correctly separated the work between developers of the team, they will be focusing on one or more parts of the application, but do not need to know every single bit of what the application is doing.
In a perfect world, the application is split in various `{shiny}` modules, one module equals one file, and each member of the team will be assigned to the development of one or more modules.
In a perfect world, the application is split into various `{shiny}` modules, one module equals one file, and each member of the team will be assigned to the development of one or more modules.

It is simpler to work in this context where one developer is assigned to one module, although we know that in reality it may be a little more complex, and several members of the team might go back and forth working on a common module.
But the person in charge will be there to help make all the pieces fit together.
Loading
Loading