我对Visual Studio的行数计算方式感到困惑,因为我看到的结果与报告的结果不一致。所以我写了一个小的C#控制台程序来计算纯代码行数,并将结果写入CSV文件中(见下文)。
打开一个新的解决方案,将其复制并粘贴到Program.cs文件中,构建可执行文件,然后你就可以开始使用了。这是一个.Net 3.5应用程序。将其复制到代码库的最顶层目录中。打开命令窗口并运行可执行文件。你会得到两个提示,第一个是程序/子系统的名称,第二个是你想要分析的任何额外文件类型。然后它会将结果写入当前目录下的CSV文件中。这是一个非常简单实用的工具,适合你的需求或交给管理层使用。
总之,这就是它,供参考,但效果因人而异:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
namespace CodeMetricsConsole
{
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine();
String userInput_subSystemName;
Console.Write("Enter the name of this application or subsystem (required): ");
userInput_subSystemName = Console.ReadLine();
if (userInput_subSystemName.Length == 0)
{
Console.WriteLine("Application or subsystem name required, exiting.");
return;
}
Console.WriteLine();
String userInput_additionalFileTypes;
Console.WriteLine("Default extensions are asax, css, cs, js, aspx, ascx, master, txt, jsp, java, php, bas");
Console.WriteLine("Enter a comma-separated list of additional file extensions (if any) you wish to analyze");
Console.Write(" --> ");
userInput_additionalFileTypes = Console.ReadLine();
Console.WriteLine();
Console.WriteLine("Getting LOC counts...");
Console.WriteLine();
HashSet allowedExtensions = new HashSet { "asax", "css", "cs", "js", "aspx", "ascx", "master", "txt", "jsp", "java", "php", "bas" };
String[] additionalFileTypes;
String[] separator = { "," };
if (userInput_additionalFileTypes.Length > 0)
{
additionalFileTypes = userInput_additionalFileTypes.Split(separator, StringSplitOptions.RemoveEmptyEntries);
foreach (String ext in additionalFileTypes)
{
try
{
allowedExtensions.Add(ext.Trim());
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
}
}
String summaryFile = userInput_subSystemName + "_Summary.csv";
String path = Directory.GetCurrentDirectory();
String pathAndFile = path + Path.DirectorySeparatorChar + summaryFile;
Regex oneLineComment = new Regex(@"^\s*//");
Regex startBlockComment = new Regex(@"^\s*/\*.*");
Regex whiteSpaceOnly = new Regex(@"^\s*$");
Regex code = new Regex(@"\S*");
Regex endBlockComment = new Regex(@".*\*/");
Regex oneLineBlockComment = new Regex(@"^\s*/\*.*\*/.*");
Regex multiLineStringStart = new Regex("^[^\"]*@\".*");
Regex multiLineStringEnd = new Regex("^.*\".*");
Regex oneLineMLString = new Regex("^.*@\".*\"");
Regex vbaComment = new Regex(@"^\s*'");
FileStream fs = null;
String line = null;
int codeLineCount = 0;
int commentLineCount = 0;
int wsLineCount = 0;
int multiLineStringCount = 0;
int fileCodeLineCount = 0;
int fileCommentLineCount = 0;
int fileWsLineCount = 0;
int fileMultiLineStringCount = 0;
Boolean inBlockComment = false;
Boolean inMultiLineString = false;
try
{
using (StreamWriter outFile = new StreamWriter(pathAndFile, false))
{
outFile.WriteLine("filename, codeLineCount, commentLineCount, wsLineCount, mlsLineCount");
foreach (String allowed_extension in allowedExtensions)
{
String extension = "*." + allowed_extension;
codeLineCount = 0;
commentLineCount = 0;
wsLineCount = 0;
multiLineStringCount = 0;
String[] fileList = Directory.GetFiles(Directory.GetCurrentDirectory(), extension, SearchOption.AllDirectories);
for (int i = 0; i < fileList.Length; i++)
{
fileCodeLineCount = 0;
fileCommentLineCount = 0;
fileWsLineCount = 0;
fileMultiLineStringCount = 0;
inBlockComment = false;
inMultiLineString = false;
try
{
fs = new FileStream(fileList[i], FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using (TextReader tr = new StreamReader(fs))
{
while ((line = tr.ReadLine()) != null)
{
if (inBlockComment)
{
if (whiteSpaceOnly.IsMatch(line))
{
fileWsLineCount++;
}
else
{
fileCommentLineCount++;
}
if (endBlockComment.IsMatch(line)) inBlockComment = false;
}
else if (inMultiLineString)
{
fileMultiLineStringCount++;
if (multiLineStringEnd.IsMatch(line)) inMultiLineString = false;
}
else
{
if (oneLineComment.IsMatch(line))
{
fileCommentLineCount++;
}
else if (oneLineBlockComment.IsMatch(line))
{
fileCommentLineCount++;
}
else if ((startBlockComment.IsMatch(line)) && (!(oneLineBlockComment.IsMatch(line))))
{
fileCommentLineCount++;
inBlockComment = true;
}
else if (whiteSpaceOnly.IsMatch(line))
{
fileWsLineCount++;
}
else if (oneLineMLString.IsMatch(line))
{
fileCodeLineCount++;
}
else if ((multiLineStringStart.IsMatch(line)) && (!(oneLineMLString.IsMatch(line))))
{
fileCodeLineCount++;
inMultiLineString = true;
}
else if ((vbaComment.IsMatch(line)) && (allowed_extension.Equals("txt") || allowed_extension.Equals("bas"))
{
fileCommentLineCount++;
}
else
{
fileCodeLineCount++;
}
}
}
outFile.WriteLine(fileList[i] + ", " + fileCodeLineCount + ", " + fileCommentLineCount + ", " + fileWsLineCount + ", " + fileMultiLineStringCount);
fs.Close();
fs = null;
}
}
finally
{
if (fs != null) fs.Dispose();
}
codeLineCount = codeLineCount + fileCodeLineCount;
commentLineCount = commentLineCount + fileCommentLineCount;
wsLineCount = wsLineCount + fileWsLineCount;
multiLineStringCount = multiLineStringCount + fileMultiLineStringCount;
}
outFile.WriteLine("Summary for: " + extension + ", " + codeLineCount + ", " + commentLineCount + ", " + wsLineCount + ", " + multiLineStringCount);
}
}
Console.WriteLine("Analysis complete, file is: " + pathAndFile);
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
}
}
catch (Exception e2)
{
Console.WriteLine("Error: " + e2.Message);
}
}
private void testRegex(Regex rx)
{
String test = " asdfasd asdf @\" adf ++--// /*\" ";
if (rx.IsMatch(test))
{
Console.WriteLine(" -->| " + rx.ToString() + " | matched: " + test);
}
else
{
Console.WriteLine("No match");
}
}
}
}
这是它的工作原理:
- 程序有一组您想要分析的文件扩展名。
- 它遍历集合中的每个扩展名,在当前目录和所有子目录中获取该类型的所有文件。
- 它选择每个文件,遍历该文件的每一行,将每一行与正则表达式进行比较以确定它正在查看什么,并在确定它正在查看什么后递增行计数。
- 如果一行不是空格、单行或多行注释或多行字符串,则将其视为代码行。它报告每种类型行(代码、注释、空格、多行字符串)的所有计数,并将它们写入CSV文件中。无需解释为什么Visual Studio会或不会将某些内容视为代码行。
是的,其中嵌套了三个循环(O(n-cubed) O_O),但它只是一个简单的独立开发者工具,我运行它的最大代码库约为350K行,它在Core i7上运行大约需要10秒钟。
编辑:刚在Firefox 12代码库上运行了一下,大约有430万行(330万代码,100万注释),大约21K个文件,使用AMD Phenom处理器-花费了7分钟,在任务管理器的性能选项卡中观察,没有压力。供参考。
我的态度是,如果我写的是编译器指令的一部分,那么它就是一行代码,应该计入其中。
它可以很容易地定制忽略或计算任何你想要的内容(括号、命名空间、文件顶部的包含等)。只需添加正则表达式,使用正则表达式下面的函数进行测试,然后更新if语句中的正则表达式即可。
Test
方法中删除out
参数并更新测试代码会导致LOC报告为2,我认为这是正确的。添加out
会导致跳转,因此不是由于大括号或属性引起的。 - Ian Newson