Skip to contents

What is technical debt?

Technical debt is the cost of additional work caused by shortcuts in the code: quick fixes, untested code, poor naming, unnecessary complexity. Like financial debt, it accumulates interest if not repaid.

rsonar quantifies this debt in estimated remediation minutes, using the SQALE (Software Quality Assessment based on Lifecycle Expectations) model used by SonarQube.


The SQALE Model in rsonar

Debt calculation

The total debt is the sum of costs per category:

Dtotal=Dlint_err+Dlint_warn+Dstyle+Dcoverage+DgpD_{total} = D_{lint\_err} + D_{lint\_warn} + D_{style} + D_{coverage} + D_{gp}

With default costs:

Category Default unit cost
Lint error (error) 30 min
Lint warning (warning) 10 min
Lint style violation 2 min
Improperly formatted file (styler) 5 min
Goodpractice failure 20 min
Missing coverage point 5 min/point

SQALE rating calculation

The rating is computed from the debt-to-base-effort ratio:

ratio=DtotalNfiles×30 minratio = \frac{D_{total}}{N_{files} \times 30 \text{ min}}

Ratio Rating Meaning
< 5% A Excellent quality
5–10% B Good quality
10–20% C Acceptable quality
20–50% D Poor quality
> 50% E Critical

quality_score() exposes the same debt ratio as an easy-to-read percentage for IDE usage:

score=100×(1min(1,ratio))score = 100 \times (1 - min(1, ratio))


Example analysis

library(rsonar)

res  <- sonar_analyse(".")
debt <- debt_index(res)
print(debt)

# Fast local score shown in IDE
quality_score(res)
── rsonar Technical Debt ───────────────────────────────────
ℹ Estimated duration: 2.17h (130 min)
ℹ SQALE rating: C

── Breakdown by category ────────────────────────────────────
             category issues minutes
1      Lint (errors)      2      60
2     Lint (warnings)     4      40
3     Best practices      1      20
4          Coverage       2      10

Customizing costs

Default costs can be adjusted to match your organization’s context:

# Stricter costs for a critical project
debt <- debt_index(res,
  cost_lint_error     = 60,   # 1h per error
  cost_lint_warning   = 15,
  cost_style          = 10,
  cost_gp             = 30,
  coverage_target     = 90,   # Target: 90%
  cost_coverage_point = 8
)

Remediation strategy

Prioritization: return on investment

Sort by debt_minutes / number_of_corrections_needed ratio:

debt <- debt_index(res)

# Display categories with the most debt
breakdown <- debt$breakdown[order(-debt$breakdown$minutes), ]
print(breakdown)

Progressive approach (legacy debt)

For an existing project with a lot of debt, adopt an incremental approach:

# Week 1: fix lint errors (highest impact)
# Week 2: add tests to increase coverage
# Week 3: reformat the code (styler::style_dir)
# Week 4: address warnings and goodpractice

# Track debt evolution over time
debt_week_1  <- debt_index(res_before)$minutes
debt_week_4  <- debt_index(res_after)$minutes
reduction_pct <- round(100 * (1 - debt_week_4 / debt_week_1), 1)
cat("Debt reduction:", reduction_pct, "%\n")

Comparing analyses with sonar_diff()

Use sonar_diff() to see exactly what changed between two analysis runs:

baseline <- sonar_analyse(".")
# ... make improvements ...
current  <- sonar_analyse(".")
diff     <- sonar_diff(current, baseline)
print(diff)

This shows delta metrics, new issues introduced, and issues fixed.


Trend tracking in CI

Using sonar_trend()

sonar_trend() appends each analysis to a JSON history file, making it easy to monitor quality evolution across builds:

res <- sonar_analyse(".")
sonar_trend(res, file = "rsonar-history.json")

In your .gitlab-ci.yml:

rsonar-report:
  stage: report
  when: always
  script:
    - |
      Rscript -e "
      library(rsonar)
      res <- sonar_analyse('.')
      sonar_trend(res)
      sonar_report(res, output='rsonar-report.html', open=FALSE)
      "
  artifacts:
    paths:
      - rsonar-report.html
      - rsonar-history.json
    expire_in: 4 weeks

GitLab — rating as a metric

In your .gitlab-ci.yml, expose the rating as a metric:

rsonar-metrics:
  script:
    - |
      Rscript -e "
      library(rsonar)
      res  <- sonar_analyse('.')
      debt <- debt_index(res)
      cat('SQALE rating:', debt\$rating, '\n')
      cat('Debt minutes:', debt\$minutes, '\n')
      # Fail if rating is D or E
      if (debt\$rating %in% c('D', 'E'))
        stop('Insufficient SQALE rating: ', debt\$rating)
      "

Best practices for maintaining low debt

  1. Configure .lintr from the start of the project with use_rsonar_lintr()
  2. Format systematically before each commit: styler::style_dir("R")
  3. Write tests alongside code, not after
  4. Block regressions in CI with quality_gate(fail_on_error = TRUE)
  5. Don’t accumulate commented code — use Git history
  6. Name clearly functions and variables — object_length_linter(30)

The best way to manage technical debt is not to accumulate it.