源程序量的定义与重要性
源程序量是衡量软件源代码规模的指标,用于评估开发工作量、成本、复杂度和维护难度。它关注程序员编写的代码,而非编译后或运行时的文件。
主要测量方式
代码行数(LOC)
物理行数统计源文件的总行数,包括空行和注释。逻辑行数统计可执行语句的行数,排除空行和注释,更能反映实际功能规模。
千行代码数(KLOC)
常用于大型项目,例如“50 KLOC”表示约5万行代码。
功能点(FP)
与编程语言无关,通过评估用户功能数量(如输入、输出、查询)计算规模。可按语言系数转换为预计代码行数。
其他相关度量
注释率反映代码可读性,代码密度衡量单位行数的功能复杂度,文件数反映项目结构复杂度。
用途与注意事项
项目估算与规划
历史项目的“代码行/人月”数据可用于估算新项目的时间和人力成本。
生产力评估
需谨慎使用,代码行数多不一定代表生产力高或质量好,可能意味着冗余或低效。
质量预测
规模大的模块通常更复杂,缺陷也可能更多。常与缺陷密度(每千行代码的缺陷数)结合使用。
局限性
不能单独衡量质量或效率,受编程语言、风格和复用度影响。不同语言实现同一功能的代码行数差异显著。
排除第三方依赖
源程序量应排除第三方代码,仅统计自研代码。例如,50 KLOC自研代码加500 KLOC第三方库,项目管理应基于50 KLOC。
排除的文件和目录
生成/编译产物
如Flutter的*.g.dart、protobuf的*.pb.dart、注解处理器生成的代码(Lombok, Dagger)。
资源文件
图片(*.png)、字体(*.ttf)、配置文件(*.json)、证书(*.keystore)、本地化文件(strings.xml)。
文档和测试数据
Markdown文件(*.md)、示例数据(sample_data/)、数据库文件(*.db)。
构建脚本和工具链文件
构建脚本(Makefile)、持续集成配置(.github/workflows/)、包管理配置(package.json)。
IDE和编辑器文件
项目配置(.idea/)、缓存文件(*.iml)。
版本控制相关
忽略文件(.gitignore)、钩子脚本(.git/hooks/)。
临时文件和日志
日志文件(*.log)、临时文件(tmp/)。
统计代码行数的命令示例
前端统计命令
$extensions = @('*.js', '*.jsx', '*.ts', '*.tsx', '*.vue', '*.dart', '*.java', '*.kt', '*.swift', '*.m', '*.h') $excludeDirs = @('node_modules', 'unpackage', '.hbuilderx', '.git', 'build', 'dist', 'Pods', 'android/app/build', 'ios/Pods', '*.iml', '.idea', '.vscode') $files = Get-ChildItem -Path .\ -Include $extensions -Recurse -File | Where-Object { $exclude = $false foreach ($dir in $excludeDirs) { if ($_.FullName -match [regex]::Escape($dir)) { $exclude = $true break } } -not $exclude } | Select-Object -ExpandProperty FullName $total = 0 $fileCount = 0 $files | ForEach-Object { $lineCount = (Get-Content $_ | Measure-Object -Line).Lines $total += $lineCount $fileCount++ } Write-Output "总文件数: $fileCount" Write-Output "总代码行数: $total (约 $([math]::Round($total/1000, 2)) KLOC)"后端统计命令
$excludeDirs = @( "**/target/", "**/.git/", "**/.idea/", "**/logs/", "**/tmp/", "**/temp/", "**/sample_data/", "**/fixtures/", "**/mock_data/" ) $excludeFiles = @( "*.g.dart", "*.pb.dart", "*.generated.*", "AutoGenerated.*", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.svg", "*.ico", "*.mp4", "*.mp3", "*.wav", "*.ogg", "*.ttf", "*.otf", "*.woff", "*.woff2", "*.json", "*.xml", "*.plist", "*.yaml", "*.yml", "*.properties", "*.keystore", "*.p12", "*.cer", "*.pfx", "*.md", "*.txt", "*.pdf", "*.docx", "*.xlsx", "*.db", "*.sqlite", "*.sql", "Makefile", "CMakeLists.txt", "*.gradle", "*.pro", "*.log", "*.tmp", "*.iml", "*.suo", "*.swp", "*.class", "*.jar", "*.war", "*.ear", "*.zip", "*.tar", "*.gz" ) $rootPath = "d:\Dreamsss\painting-dreams" $fileCount = 0 $totalLines = 0 $totalCodeLines = 0 $totalBlankLines = 0 $totalCommentLines = 0 $sourceExtensions = @(".java", ".kt", ".py", ".js", ".ts", ".jsx", ".tsx", ".go", ".rs", ".cpp", ".h", ".c", ".hpp", ".cs", ".php", ".rb", ".swift") function Get-FilesRecursively($path) { Get-ChildItem -Path $path -File -Recurse -Force | Where-Object { $relativePath = $_.FullName.Substring($rootPath.Length + 1).Replace("\", "/") $inExcludedDir = $false foreach ($dir in $excludeDirs) { if ($relativePath -like $dir -or $relativePath -like $dir) { $inExcludedDir = $true break } } -not $inExcludedDir } }