Best Practices for Durable R Code

By:
Appsilon Team
September 27, 2021

Whether you're just starting your journey to developing a successful app or riding a wave of adoption, it's a good idea to build durable R code. Projects that are built with durable code are built to adapt.  Having well-thought-out coding practices in place doesn't prevent the challenges of growing pains, but it can certainly make resolving them a lot easier. Users add new insights to projects, exposing blind spots and ideas either missed by devs or placed on hold for priority management. Of course, hindsight is 20/20, but if you can start the development process with a strong foundation of durable code, the more likely your project will endure and be easier to update. <blockquote><strong>Build, style, and scale your Shiny application with <a href="https://www.youtube.com/watch?v=MYVojGHeKAc" target="_blank" rel="noopener noreferrer">Appsilon's Shiny Masterclass</a></strong></blockquote> In this blog, we'll take you through some best practices you should consider for building durable R code. <ul><li><a href="#anchor-1" rel="noopener noreferrer">Video presentation</a></li><li><a href="#anchor-2" rel="noopener noreferrer">Code encapsulation</a></li><li><a href="#anchor-3" rel="noopener noreferrer">Tests</a></li><li><a href="#anchor-4" rel="noopener noreferrer">Project structure</a></li></ul> <h2 id="anchor-1">Video Presentation: Best Practices for Durable R Code</h2> <iframe title="YouTube video player" src="https://www.youtube.com/embed/sgJVk80X2EQ" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe> <h2 id="anchor-2">Code encapsulation</h2> Code encapsulation is taking code, breaking it down into pieces, and putting them into separate modules. To put it simply, it's bundling variables and methods as a single unit. This is a great method for organizing code, making it easier for devs to find and easily modify when needed. In general, it reduces the repetition of information by following the DRY principle. <blockquote><strong>Don't let UX design be an afterthought. Learn how to <a href="https://appsilon.com/ux-design-of-shiny-apps-7-steps-to-design-dashboards-people-love/" target="_blank" rel="noopener noreferrer">design dashboards that people love</a>. </strong></blockquote> There are several ways to encapsulate R code, but for this blog, we'll stick to three of our favorites: <ol><li><a href="https://shiny.rstudio.com/reference/shiny/1.5.0/moduleServer.html" target="_blank" rel="noopener noreferrer nofollow">Shiny modules</a> - a pairing of UI and server, embedded into Shiny. You can use it wherever you have repeatable Shiny elements.</li><li><a href="https://cran.r-project.org/web/packages/box/index.html" target="_blank" rel="noopener noreferrer nofollow">Box</a> - a package for organizing and modularizing source code for easy use across projects. Best when you have repeatable code.</li><li><a href="https://r6.r-lib.org/articles/Introduction.html" target="_blank" rel="noopener noreferrer nofollow">R6 classes</a> - an implementation of OOP for R and slightly more complicated than the other methods described. R6 classes are efficient ways to place single-use objects with related logic while supporting public and private methods.</li></ol> <h3>Shiny modules</h3> To use Shiny modules, you'll first need to write UI and server functions. For the UI create an ID and initiate a namespace. This will consist of UI code that you write in your Shiny app using the ID from that namespace. Similarly, in the server, initiate the namespace and write standard server code with IDs from that namespace. <script src="https://gist.github.com/MicahAppsilon/b148e63cc031a3b213e428afebb95e51.js"></script> <script src="https://gist.github.com/MicahAppsilon/2f32bb74475c2cbaed55becc008c07fb.js"></script> <blockquote><strong>Speed up production with Appsilon's open-source <a href="https://appsilon.com/shiny-templates-available/" target="_blank" rel="noopener noreferrer">Shiny dashboard templates</a>.</strong></blockquote> Once you have those two functions, you need to embed them in the shiny app. To do this, go to the UI of your Shiny app, and use the UI function directly with the unique ID. In the server, use the core module function that provides the server function you just wrote and the id to match with your UI and any additional arguments if your server function requires them. <script src="https://gist.github.com/MicahAppsilon/01fe829b9fe3adf9b58feefae7550d3e.js"></script> <h3>Box package</h3> Box is helpful when reusing functions across an application. Any extracted R files or project folders can be set as reusable modules and nested. Use is straightforward. Simply write your usual code, add<code class="language-r">#' @export</code>, and import the modules or packages with<code class="language-r">box::use</code>. In the example below, we'll first import dependencies using the 'box way': <script src="https://gist.github.com/MicahAppsilon/1fa2c5f54d426c6c572b3ad66fc19e36.js"></script> Next, if we want to reuse the module, we'll need to import the function. Again, using the 'box way.' And when you want to use the function, simply call and use it. Note: if you try to access any internal functions that were not exported, you will receive an error as they cannot be used outside the module. <script src="https://gist.github.com/MicahAppsilon/c6a7af976ba6aaae80deff7168caffab.js"></script> <h3>R6 classes</h3> R6 classes are a bit more complicated than the previous methods, but well-developed documentation can be found in <a href="https://adv-r.hadley.nz/r6.html" rel="nofollow">Hadley’s Advanced R</a>. The definition of the class car is found below: <script src="https://gist.github.com/MicahAppsilon/e0c52d3ceb91a236c20faf188a4e8cb3.js"></script> In each class, there are fields and methods. Fields are similar to variables and methods are similar to functions. And both can be listed as private or public  (private meaning availability only within the class). To generate an object in the class we will make use of the$newfunction and then initialize. <script src="https://gist.github.com/MicahAppsilon/175e64bd5f9abeb814c3bf1b13b7bfe5.js"></script> <h2 id="anchor-3">Tests - checking for durable R code</h2> Tests are so important and if you're not implementing them in your development process, chances are you're missing improvements to your application or bugs that lead to wrong results. Testing can help a project in many facets of development from ensuring the highest quality of output to maximizing cost- and time-efficiency, as well as making debugging easier. No matter the project, we always recommend making good use of various types of testing. <blockquote><strong>Are your user tests effective? <a href="https://appsilon.com/user-tests-build-better-shiny-apps-with-effective-user-testing/" target="_blank" rel="noopener noreferrer">Build better Shiny apps with effective user testing</a>.</strong></blockquote> <h3>Unit testing with testthat</h3> We recommend testthat for testing your code. It can be easily integrated into existing workflows and makes for an engaging method for testing. In the example below, we will start with a function to test. <script src="https://gist.github.com/MicahAppsilon/83641aec32e3a3980f3226b868475f35.js"></script> Then we create a file with the test, where we can describe the expected outcomes of the function in the<code class="language-r">context()</code>. <script src="https://gist.github.com/MicahAppsilon/cb596141e69d3f8cf32f4dc5cc3a3129.js"></script> Two quick tips for those who are new to unit testing: <ol><li style="font-weight: 400;" aria-level="1">Make sure you’re unit tests are limited to one function </li><li style="font-weight: 400;" aria-level="1">Make sure you select varying data for different use cases</li></ol> <h3>User interviews</h3> One particularly useful method of testing is user interviews. Interviews can often be underappreciated or undervalued by developers, but they play a valuable role in good app development. User interviews allow you the chance to discover conceptual debt you may have in your app. No one knows your project better than you, but you might be so wrapped up in the dev and design that some things aren't as logical to others as they are to you. This is your opportunity to check that your app is intuitive as a whole or if specific components are understandable and make sense.  We recommend following a few Golden rules for interviews, but this is by no means an exhaustive list: <ol><li style="font-weight: 400;" aria-level="1">Keep it 1-on-1</li><li style="font-weight: 400;" aria-level="1">Make it the first contact with the app</li><li style="font-weight: 400;" aria-level="1">Prepare your script in advance</li><li style="font-weight: 400;" aria-level="1">Explain the specific goal(s) to the user</li><li style="font-weight: 400;" aria-level="1">Ask business questions to make them solve specific tasks</li><li aria-level="1"><strong>Don't help</strong> the user to accomplish tasks by leading them (no matter how tempting)</li></ol> And as a general note, be sure that the user understands that you are not testing them, but rather the application. A user interview is a test of an app's functionality, not a user's abilities. <blockquote><strong>Is your Shiny app experiencing growing pains? Gain valuable performance insights by learning <a href="https://appsilon.com/how-to-pull-shiny-usage-data-from-rstudio-connect-api-setup-guide/" target="_blank" rel="noopener noreferrer">how to pull Shiny usage data from RStudio Connect.</a></strong></blockquote> <h2 id="anchor-4">Project structure</h2> Taking the time to establish a good project structure enables you to build a well-organized project for you, your team, and others to easily cooperate. It ensures that everyone knows where files are stored and how to manage them, which in turn leads to faster development. No matter the size of the project, new team members should be able to locate and contribute as quickly as possible without additional training. Every project is unique in its setup and requirements. A project's structure should be treated as a function of how to best serve the end goal and not be the end-all-be-all. That being said, there are a few common points to consider. <ol><li>A project's <strong>README.md</strong> should be maintained and <strong>updated often</strong>. It should exist throughout the entire life of the project.</li><li>Store your Shiny modules and R6 classes under <strong>modules</strong> in the app catalog. And of course, use <strong>descriptive names</strong> and group-related modules together.</li><li>In the <strong>utils</strong> catalog, you'll likely need more structure to group pieces of code such as <strong>helper functions</strong> that will be imported in modules or global files using the Box package</li><li>Tests (oh yes!) catalog at Appsilon usually includes a <a href="https://cran.r-project.org/web/packages/lintr/readme/README.html" target="_blank" rel="noopener noreferrer nofollow">lintr</a>.R to check our code style is being adhered to and additional tests such as testthat, where we store the files that run unit tests.</li></ol> <h2>Watch, learn, and build durable R code</h2> Appsilon prides itself on serving the data science and R community. We've spent years building workflows, data science solutions, and <a href="https://appsilon.com/shiny/" target="_blank" rel="noopener noreferrer">Shiny applications</a> for Fortune 500 companies. Along the way, we've developed several <a href="https://appsilon.com/opensource/" target="_blank" rel="noopener noreferrer">open source packages</a> and free-to-download <a href="https://templates.appsilon.com/" target="_blank" rel="noopener noreferrer">Shiny dashboard templates</a>. We want to make sure R users have everything they need to improve their data science applications. But we also understand that it can be challenging to build enterprise-level applications in R and Shiny. Our team of expert R/Shiny developers, software engineers, UX designers, and business analysts can tackle the most challenging data science problems and do it quickly. If you need help with your enterprise application, reach out to us. We love finding solutions to unique data science problems.

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!

Is Your Software GxP Compliant?

Download a checklist designed for clinical managers in data departments to make sure that software meets requirements for FDA and EMA submissions.
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
shiny
r
community
user tests
shiny modules
x-session