Skip to content

Coding Standards

Paul Carvalho edited this page Jul 15, 2025 · 9 revisions

Coding Standards for FishSET GUI

This is a set of coding standards to follow to ensure that FishSET is well-organized and easily maintainable.

1. Consistent Naming Conventions

  • Variables: use snake_case across the entire app (example: user_input). DONOT use camelCase for variables.
  • UI elements: name UI components using a prefix to indicate the type of element. For example, input_name or output_plot.
  • Reactive variables: name with prefix rv_ (example: rv_data).
  • Buttons: name buttons with suffix _btn (example: run_model_btn).
  • Modules: name of module with suffix _ui or _server (exmaple: zone_closure_ui and zone_closure_server)

2. Code Modularity

  • Modularize logic: break code into small, reusable functions, especially if logic is reused across different parts of the app. For example, move data preprocessing into its own function in a separate R script in the /R folder.
  • Use shiny modules: Split the UI and server logic into Shiny modules. This improves scalability, reusability, and maintainability. For example, mod_input_ui, mod_input_server.

3. Reactive Programming Best Practices

  • Minimize reactivity: keep the number of reactive expressions (reactive(), observe(), observeEvent()) to a minimum to avoid performance issues.
  • Avoid nested reactivity (if possible): if a reactive value depends on other reactive values, try to simplify the hierarchy of dependencies to reduce complexity.
  • Use req(): always check for the presence of input values using req() in server-side logic to ensure that no invalid inputs are processed.

4. UI Design

  • Use bslib::page_fillable(), bslib::page_fluid() ,fluidRow, fillable = TRUE, fill = TRUE: make the UI responsive with these functions to ensure proper layout in different screen sizes.
  • Keep UI clean and minimal: avoid overloading the user interface with too many elements. Only include essential inputs, outputs, and information.
  • use CSS document to add themes and other styling elements
  • Use appropriate UI elements: choose the right UI element for the task. For example, UI input categories.

5. Performance Optimization

  • Use session$onSessionEnded(): clean up any long-running processes or background tasks when the session ends.
  • Use shinyjs or shinybusy for asynchronous operations: for better user experience, indicate busy states or process long-running tasks in the background.
  • Caching and memorization: use reactiveCache or similar strategies to cache expensive operations when the input parameters have not changed.
  • Avoid unnecessary render calls: don't wrap render* functions around things that don't need to be reactive, like static text.
  • Efficient state management: use reactiveVal() and reactiveValues() instead of re-triggering reactivity each time a value needs to be updated across the app.
  • Use the profiler on computationally expensive tasks to optimize code.
  • For computationally expensive tasks that disable the shiny while running, use ExtendedTask to run the function in the background and allow users to continue in the app.

6. Code Documentation and Comments

  • Document your code: user clear and concise comments to explain sections of code, especially for complex logic. Use roxygen-style documentation for functions in separate files.
  • Describe inputs/outputs: for each reactive input or output, make sure to describe what it does, and document the expected input and output types.

7. Use of Packages

  • Use bslib for improved UI: use the bslib package for more polished user interfaces.
  • Use shinytest to testing: write unit tests for Shiny app with the shinytest package to ensure the app works as expected.

8. Error Handling

  • Use tryCatch(): when there is a chance that an error might occur (e.g., data loading or processing), use the tryCatch() function to handle errors.
  • Custom error messages: provide clear, user-friendly error messages in the UI for cases where things go wrong (e.g., when inputs are invalid).

9. Version Control

  • Use Git: this is essential for collaboration, tracking changes, and maintaining the app over time. Try to keep pull requests to a manageable size. If the changes made in a pull request are extensive it can be difficult to review. In this case, break up the pull request/issue into smaller sections.
  • Use .gitignore: avoid committing large or unnecessary files like data files or temporary files created during development by adding them to .gitignore.

10. Unit Testing

  • Test files are located in the FishSET/tests/testthat folder - aim to have unit tests for each script in the R/ folder (and tests for each function in the script if there are multiple).
  • Test file naming convention is test-[rscript].R. For example, the test file associated with the sql_functions.R script is test-sql_functions.R. Files can be automatically generated and placed within the testthat folder by running the usethis::use_test("Rscript.R") function.
  • Shinytest2 tests are skipped during the automated R CMD check because the temporary testing environment is too restrictive. Run all unit tests including shinytest2 tests by running devtools::test() or click on test in the build tab in RStudio.
  • The test-shiny-app.R script in FishSET/tests/testthat run shiny tests defined in the FishSET/inst/ShinyFiles/MainApp/tests/testthat/test-shinytest2.R script. Add new shiny tests to test-shinytest2.R, also any tests generated using shinytest2::record_test() will be automatically added to this file.