Sloc Cloc and Code 4.0 (scc) - Het vinden van de bestanden die de meeste aandacht nodig hebben
In dit bericht loop ik een aantal van deze functies langs, in de hoop lezers aan te moedigen de nieuwste versie uit te proberen.
Hotspots
Meer dan tien jaar geleden schreef ik over de bug-voorspelling van Google, die bestanden rangschikte op basis van commit-geschiedenis versus bugfixes om te bepalen waar problematische bestanden zich bevonden. Het was interessant, maar werd stopgezet omdat ontwikkelaars het simpelweg niet nuttig vonden. Soms wisten ze dat de code een 'hotspot' was, soms niet, maar het weten dat iets een hotspot was, bood hen geen middelen om tot verbetering over te gaan.
Onlangs vroeg ik me af of we de complexiteitsschatting van scc kunnen gebruiken om de ruis te verminderen. Het weten dat er veel fixes zijn toegepast op een configuratiebestand is niet erg nuttig; echter, weten dat er veel wijzigingen zijn aangebracht in een bestand met veel logica, is dat wel. Dit is dezelfde aanpak die ik gebruikte voor de rangschikking in codespelunker.
Complexe bestanden hebben de meeste aandacht nodig!
Voor zover ik weet is dit een heruitvinding van een idee van Adam Tornhill in "Your Code as a Crime Scene" (een boek dat ik momenteel lees), wat uiteindelijk leidde tot de oprichting van het bedrijf CodeScene. Er zit duidelijk waarde in deze metriek.
Hier is een voorbeeld van de output wanneer scc op zijn eigen codebase wordt gedraaid:
$ scc --hotspots
───────────────────────────────────────────────────────────────────────────────
Hotspots · last 1000 commits · 2019-07-21 → 2026-06-26
───────────────────────────────────────────────────────────────────────────────
File Lang Cmplx Commits Lines± Authrs Hotspot
───────────────────────────────────────────────────────────────────────────────
processor/processor.go Go 156 156 1,651 13 100.0
processor/workers.go Go 244 92 3,617 15 92.2
test-all.sh Shell 56 181 3,287 15 41.7
~ocessor/formatters_test.go Go 183 51 2,459 9 38.4
processor/workers_test.go Go 408 21 1,189 8 35.2
processor/formatters.go Go 44 135 5,848 17 24.4
main_test.go Go 261 18 995 6 19.3
processor/detector_test.go Go 133 32 1,175 6 17.5
main.go Go 40 101 1,545 17 16.6
processor/file.go Go 50 73 2,074 12 15.0
processor/detector.go Go 70 45 948 5 12.9
processor/file_test.go Go 75 33 826 5 10.2
cmd/badges/main.go Go 73 31 1,111 5 9.3
processor/history.go Go 173 9 941 4 6.4
processor/structs.go Go 25 42 247 10 4.3
config_test.go Go 199 4 850 3 3.3
~workers_regression_test.go Go 50 13 276 6 2.7
~rocessor/processor_test.go Go 51 12 289 4 2.5
~ocessor/history_authors.go Go 111 5 666 3 2.3
mcp.go Go 71 7 527 4 2.0
───────────────────────────────────────────────────────────────────────────────
complexity × change-frequency, normalised · 20 of 90 files shown
───────────────────────────────────────────────────────────────────────────────
Zoals je kunt zien, heeft de output correct geïdentificeerd dat processor/processor.go en processor/workers.go de hotspots in de codebase zijn.
Hoe werkt het?
Kort samengevat: hotspot = complexiteit × aantal commits, genormaliseerd op een schaal van 0-100. We berekenen de complexiteit voor het huidige HEAD-bestand en lopen vervolgens terug om te zien hoe vaak elk bestand is gewijzigd. Let op: alleen bestanden in HEAD worden geteld; bestanden met veel wijzigingen die inmiddels zijn verwijderd, tellen niet mee.
Ter vergelijking met een eenvoudige telling:
$ scc --by-file -i go -s complexity
───────────────────────────────────────────────────────────────────────────────
Language Files Lines Blanks Comments Code Complexity
───────────────────────────────────────────────────────────────────────────────
Go 69 40,137 3,049 2,131 34,957 4,478
───────────────────────────────────────────────────────────────────────────────
processor/workers_test.go 2,156 374 69 1,713 408
main_test.go 992 80 26 886 261
processor/workers.go 966 146 102 718 244
processor/report_test.go 971 98 109 764 237
config_test.go 786 50 85 651 199
Bij een eenvoudige telling, beperkt tot Go-bestanden en gesorteerd op complexiteit, scoren workerstest.go, maintest.go en config_test.go hoog. Deze bestanden zijn technisch complex, maar ze vormen niet de plek waar het zware ontwikkelwerk plaatsvindt. Complexiteit op zichzelf vertelt je waar grote bestanden met veel if-condities zijn; vaak zijn dit testbestanden.
Draai het om en rangschik op churn (het aantal commits). Nu is test-all.sh nummer één met 181 commits, en structs.go stijgt met 42. Beiden veranderen constant, maar geen van beide bevat de kernlogica of de bugs. Churn op zichzelf vertelt je wat er veel verandert (vaak configuraties, scripts en boilerplate), maar is geen goede proxy voor bugs of logica.
Een redelijke proxy is echter de overlap tussen churn en complexiteit: welke bestanden zijn zowel gecompliceerd als onderhevig aan veel wijzigingen? Dit is geen aanwijzing voor "historisch buggy" code, maar een indicator voor "moeilijk om mee te werken", wat suggereert dat code mogelijk opgesplitst moet worden.
Complexe code die niemand bewerkt, is waarschijnlijk geen probleem. Simpele bestanden die je constant aanpast, zijn dat meestal ook niet. Echter, een bestand dat complex is én veel verandert, is waar problemen meestal liggen: merge-conflicten, brekende tests en frustratie bij wijzigingen.
Waar Google faalde omdat een hotspot-vlag geen directe actie suggereerde, wordt een soortgelijk idee een "onboarding map" wanneer je een onbekende codebase probeert te begrijpen.
Git-diepte en beperkingen
Je kunt de diepte van de git-commits specificeren voor deze berekening. Hiermee kun je zien hoe hotspots zijn verschoven door verder of korter terug in de tijd te kijken.
Voorbeeld van 50 commits versus 10 commits:
$ scc --hotspots --depth 50
───────────────────────────────────────────────────────────────────────────────
Hotspots · last 50 commits · 2026-04-13 → 2026-06-26
───────────────────────────────────────────────────────────────────────────────
File Lang Cmplx Commits Lines± Authrs Hotspot
───────────────────────────────────────────────────────────────────────────────
processor/processor.go Go 156 14 415 4 100.0
main_test.go Go 261 8 295 4 95.6
processor/history.go Go 173 9 941 4 71.3
processor/workers.go Go 244 6 130 5 67.0
processor/workers_test.go Go 408 3 157 3 56.0
...
$ scc --hotspots --depth 10
───────────────────────────────────────────────────────────────────────────────
Hotspots · last 10 commits · 2026-06-25 → 2026-06-26
───────────────────────────────────────────────────────────────────────────────
File Lang Cmplx Commits Lines± Authrs Hotspot
───────────────────────────────────────────────────────────────────────────────
processor/workers.go Go 244 2 15 1 100.0
processor/processor.go Go 156 3 20 1 95.9
config_test.go Go 199 2 5 2 81.6
processor/history.go Go 173 1 19 1 35.5
regression_test.go Go 152 1 6 1 31.1
Houd er rekening mee dat dit niet betekent dat er per definitie iets mis is met de codebase; het is enkel een indicator van waar je zou kunnen kijken.
Dit vereist geen installatie van git op het systeem. Hoewel de repository een .git-map nodig heeft, wordt scc geleverd met github.com/go-git/go-git, waardoor het een enkelvoudig binary-installatiebestand blijft.
Prestatiekosten
Omdat scc nu terugloopt door de git-geschiedenis (standaard 1000 commits), is het proces trager dan een standaard scc-run.
$ hyperfine 'scc' 'scc --hotspots'
Benchmark 1: scc
Time (mean ± σ): 11.2 ms ± 0.4 ms [User: 15.2 ms, System: 7.6 ms]
Range (min … max): 10.6 ms … 13.5 ms 194 runs
Benchmark 2: scc --hotspots
Time (mean ± σ): 4.739 s ± 0.068 s [User: 3.341 s, System: 1.611 s]
Range (min … max): 4.707 s … 4.930 s 10 runs
Summary
scc ran
421.57 ± 14.52 times faster than scc --hotspots
(Gemeten op een Macbook Air 2020 M1 tegen de scc codebase).
Change Coupling
Geïnspireerd door CodeScene heb ik ook de change coupling functie toegevoegd. Het idee is dat bestanden afhankelijk van elkaar zijn als ze constant in dezelfde commit verschijnen, ongeacht of er een door de compiler afgedwongen afhankelijkheid bestaat.
$ scc --coupling
───────────────────────────────────────────────────────────────────────────────
Change Coupling · last 1000 commits · 2019-07-25 → 2026-07-20
───────────────────────────────────────────────────────────────────────────────
File A File B Shared Commits Coupling
───────────────────────────────────────────────────────────────────────────────
languages.json processor/constants.go 198 67.8%
LANGUAGES.md languages.json 167 61.2%
LANGUAGES.md processor/constants.go 153 58.2%
SCC-OUTPUT-REPORT.html processor/constants.go 149 37.7%
Dit is nog nuttiger wanneer het per bestand wordt toegepast:
$ scc --coupling-for ./processor/detector.go
───────────────────────────────────────────────────────────────────────────────
Change Coupling · last 1000 commits · 2019-07-25 → 2026-07-20
───────────────────────────────────────────────────────────────────────────────
Related File Shared Commits Coupling
───────────────────────────────────────────────────────────────────────────────
processor/detector_test.go 26 49.1%
processor/workers.go 15 12.1%
processor/structs.go 9 11.1%
processor/file_test.go 7 9.7%
processor/workers_test.go 6 9.7%
processor/processor_test.go 5 9.4%
Om te voorkomen dat bestanden die geen code bevatten (zoals configuraties) hoog scoren, kan de hotspots-truc (weging op basis van complexiteit) worden toegepast via --coupling-weighted. Hierdoor worden bestanden met weinig logica naar beneden gezet.
Git Metrics
Naast hotspots en coupling zijn er andere git-outputs beschikbaar:
Tijdlijn van code
Met --timeline kun je de trend van code over tijd volgen, wat handig is bij bijvoorbeeld een migratie van JS naar TS.
$ scc --timeline
───────────────────────────────────────────────────────────────────────────────
Languages · last 1000 commits · 2019-07-21 → 2026-06-26
───────────────────────────────────────────────────────────────────────────────
Language Trend Code Share Change
───────────────────────────────────────────────────────────────────────────────
Go ▂▂▂▂▂▂▂▂▂▃▃▃▃▃▃▃▃▃▃▃▃▄▆▆▆▇ 37,868 65.2% +33,595
JSON ▄▄▄▄▄▄▄▄▄▄<0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85>▆▆▆▆▆▆▆▇ 12,944 22.3% +6,236
HTML ▂▂▆▆▆▆▆▆▆▆▆▆▆▆▆▇<0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85>▆ 3,160 5.4% +3,160
Markdown ▃▃▃▃▄▄▄▄▄<0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85>▆▆▆▆▆▇ 1,884 3.2% +1,386
Shell ▂▄▄▄<0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85>▆▆▆▆▆▆▆▆▇▆<0xE2><0x96><0x85>▄ 1,200 2.1% +993
Go Template ▃▆▆▆▇ 598 1.0% +598
Python <0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85>▇ 225 0.4% +225
YAML ▃▃▃▃▃▃▃▃▃▃▃▃▆▆▆▆▆▇▇▇▇▇▇▇▇▇ 52 0.1% +33
Powershell ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 46 0.1% +0
gitignore <0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85>▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▇▇▇ 28 0.0% +9
License ▇▇▇▇▇▇<0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85><0xE2><0x96><0x85> 25 0.0% -12
Plain Text ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 24 0.0% +0
───────────────────────────────────────────────────────────────────────────────
Bus Factor
Met --by-author kun je de "bus factor" van je applicatie berekenen.
$ scc --by-author
───────────────────────────────────────────────────────────────────────────────
Authors · last 1000 commits · 2019-07-24 → 2026-07-08
───────────────────────────────────────────────────────────────────────────────
Author Code Cmplx Files Owns Last seen
───────────────────────────────────────────────────────────────────────────────
Ben Boyter (github.com) 18,901 1,839 59 37.9% 2026-07-06
apocelipes 16,740 299 11 33.6% 2026-06-26
Ben Boyter (boyter.org) 6,013 469 19 12.1% 2026-07-04
...
───────────────────────────────────────────────────────────────────────────────
Bus factor 2 · Ben Boyter (github.com) + apocelipes
last-touched 79% of in-window code
───────────────────────────────────────────────────────────────────────────────
Je kunt deze data ook combineren met de tijdlijn: scc --by-author --timeline.
Infographic
Je kunt nu een infographic genereren met scc --report. Dit produceert een HTML-bestand (scc-report.html) dat alle metrieken bevat. Dit is ideaal om aan het management te presenteren, aangezien zij waarschijnlijk geen voorkeur hebben voor een vi-geïnspireerd slide-deck.
Cognitieve Complexiteit
Scc bevat al complexiteitsberekeningen, maar cyclomatische complexiteit (uitgevonden in 1976) is niet de enige methode. Ik heb gekeken naar de benadering van Sonar, die cognitieve complexiteit berekent.
Sonar gebruikt een volledige AST (Abstract Syntax Tree), wat in scc te duur zou zijn qua runtime. Echter, de vorm van de code (inspringing/indentatie) is een goede proxy voor complexiteit. In linted code en Python is dit zeer betrouwbaar.
In scc benaderen we cognitieve complexiteit door simpelweg de hoeveelheid witruimte (spaties en tabs) aan het begin van de regel te tellen en dit te gebruiken als vermenigvuldiger voor de complexiteitsscore wanneer we op een vertakkingsconditie stuiten.
De prestatiekosten hiervan zijn verwaarloosbaar (< 1%):
$ hyperfine 'scc' 'scc --cognitive'
Benchmark 1: scc
Time (mean ± σ): 4.573 s ± 0.047 s
Benchmark 2: scc --cognitive
Time (mean ± σ): 4.583 s ± 0.086 s
Summary
scc ran 1.00 ± 0.02 times faster than scc --cognitive
Het effect is merkbaar; voor het bestand main.go in scc schiet de complexiteit omhoog:
- Standaard:
Complexity 40 - Met
--cognitive:Complexity 103
Dit is momenteel een opt-in functie en nog niet de standaard.
Config/DotFile Ondersteuning
Vanaf v4.0.0 ondersteunt scc globale configuratiebestanden. Je kunt een .sccconfig bestand toevoegen aan je projectroot of het pad definiëren via de omgevingsvariabele SCCCONFIGPATH.
Voorbeeld van een .sccconfig bestand:
# count the way I like it
--no-cocomo
--exclude-dir node_modules
--format wide
Let op: dit configuratiebestand staat scc niet toe om bestanden te schrijven; dit kan alleen via CLI-argumenten om veiligheidsredenen.
MCP Ondersteuning
Scc heeft nu ingebouwde MCP-ondersteuning. Door dit te koppelen aan een LLM, kan de LLM de complexiteit van je codebase vinden, wat helpt om tokens te besparen.
LOCOMO
Ik heb eerder over LOCOMO geschreven: scc kan nu de kosten voorspellen om je codebase in zijn huidige staat opnieuw op te bouwen met behulp van een LLM. Dit is een zelfbedachte metriek, maar ik wilde in ieder geval het proces in gang zetten.
Overige wijzigingen
- Duplicate flags: De laatste duplicate flag wint nu (bijv.
scc -i java -i gotelt alleen Go). - Taaldetectie: Linguist-geïnspireerde detectie voor betere herkenning van C++/ObjectiveC/C header-bestanden.
- JSON-output: Percentage-outputs zijn nu beschikbaar in JSON voor gebruikers van
jq. - Externe ignore-bestanden: Ondersteuning via
--ignore-file ~/.config/git/ignore. - Bugfixes & Performance: Talrijke kleine bugfixes en prestatieverbeteringen, grotendeels geleverd door apocelipes.
Conclusie
Tien jaar geleden schreef ik over Google's poging om hotspots te identificeren, wat ze stopzetten omdat men het niet nuttig vond. Ik denk dat ze niet helemaal ongelijk hadden, maar dat ze iets misten om de ruis te verminderen en het op de verkeerde plek implementeerden. Wanneer je het richt op een codebase die je probeert te doorgronden, is het veel effectiever.
Download scc 4.0.0 via GitHub, richt --hotspots op iets dat je nog nooit gezien hebt, en ontdek of het je naar de juiste bestanden stuurt.
Groetjes,