编程的水平,如同人之体质,主要来自于基础,数学与算法;
但编程的功力,如同人的武功或运动成绩,则主要来自于调试、测试与跟踪。
程序的错误大致可分为:语法错误、数据错误、逻辑错误
我们都知道,如果程序在哪行?哪列出现语法错误,Visual Studio 编译器会直接指出来,一瞅便知,非常方便。
编译器首先就是为解决语法错误而设计的工具。
数据错误主要出现于局部,或 接口部分。局部性的数据错误,通过设置断点,然后按 F10, F11 等快捷键的单步调试是不错的办法。
但很多无法进行单步调试的 接口型数据错误,是比较麻烦的。只能用下面的办法。
逻辑错误是致命的、也是最难跟踪与分析的。逻辑错误还是过程性错误,往往要在程序运行时才会出现。可是,如果是编译通过的程序,称为运行时代码,出现错误,怎么知道哪行?哪列?哪个函数出现问题呢?
你可能阅读了很多这方面的文章,但就 C# 而言,建议你使用 北京联高软件开发有限公司 在这里开源的 Truffer 的基础类之一:C#运行时逻辑错误探针类。
特点:
(1)直接输出哪个源代码?行号?列号?函数名?重点信息;
(2)插入 探针 与 撤销 都非常简单;
(3)可 分页 输出超过 20 亿条的探针信息,只要你的硬盘空间足够富裕;
(4)基于输出的 探针信息,可以做很多的分析(大数据);
(5)基于下面代码的Truffer Xtracer功能更多,比如可以检测 死循环,可以探测堆、栈及重要数据的超界等。
- #if DEBUG
- using System;
- using System.IO;
- using System.Text;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
-
- namespace Legalsoft.Truffer
- {
- public static class zz
- {
- private static StringBuilder sb { get; set; } = new StringBuilder();
- private static string head { get; set; } = "";
- private static string tail { get; set; } = "";
- private static string format { get; set; } = "";
- ///
- /// 跟踪器的步数
- ///
- private static long step { get; set; } = 0;
- ///
- /// 每个 html 文件显示的 step 数
- ///
- private static long pagesize { get; set; } = 5000;
- ///
- /// 每隔 writesize 输出跟踪器语句(html)
- /// 如果设置 writesize=0,则每个跟踪器语句都输出文件;
- ///
- private static long writesize { get; set; } = 100;
- ///
- /// 初始化
- ///
- private static void a()
- {
- if (head.Length == 0)
- {
- StringBuilder sx = new StringBuilder();
- sx.AppendLine("");
- sx.AppendLine("");
- sx.AppendLine("");
- sx.AppendLine("
");- sx.Append("
");- sx.Append("
序号 "); - sx.Append("
文件名 "); - sx.Append("
行 "); - sx.Append("
列 "); - sx.Append("
函数名 "); - sx.Append("
信息 "); - sx.AppendLine("
"); - head = sx.ToString();
- }
- if (tail.Length == 0)
- {
- StringBuilder sx = new StringBuilder();
- sx.Append("
");- sx.Append("
序号 "); - sx.Append("
文件名 "); - sx.Append("
行 "); - sx.Append("
列 "); - sx.Append("
函数名 "); - sx.Append("
信息 "); - sx.AppendLine("
"); - sx.AppendLine("
"); - sx.AppendLine("");
- sx.AppendLine("");
- tail = sx.ToString();
- }
- if (format.Length == 0)
- {
- StringBuilder sx = new StringBuilder();
- sx.Append("
");- sx.Append("
{0} "); - sx.Append("
{1} "); - sx.Append("
{2} "); - sx.Append("
{3} "); - sx.Append("
{4} "); - sx.Append("
{5} "); - sx.AppendLine("
"); - format = sx.ToString();
- }
- }
- ///
- /// 探针函数
- /// 调用方法为:
- /// zz.z(new StackTrace(new StackFrame(true)).GetFrame(0));
- ///
- /// 堆栈帧
- /// 你可以定义的任何类型的输出信息
- public static void z(StackFrame sf, string info = "")
- {
- a();
- string buf = String.Format(format, step + 1, sf.GetFileName(), sf.GetFileLineNumber(), sf.GetFileColumnNumber(), sf.GetMethod().Name, info);
- sb.AppendLine(buf);
-
- if (step > 0)
- {
- if ((step % writesize) == 0 || writesize == 0)
- {
- string fn = String.Format("Z-{0:D8}.html", (int)((step - (step % pagesize)) / pagesize));
- File.WriteAllText(fn, head + sb.ToString() + tail, Encoding.UTF8);
- }
- if ((step % pagesize) == 0)
- {
- sb.Clear();
- sb.AppendLine(buf);
- }
- }
- step++;
- }
- }
- }
- #endif
-
使用方法:
请将上面的代码保存为 Basic.Z.cs ,并加入 工程项目 project 即可。
在原来的程序中加入适当的探针代码,然后运行程序即可输出 Z-0000000.html 这样的页面文件,可以直接用浏览器阅读,了解程序调用的顺序,并分析可能存在的逻辑问题。
在需要记录(探测)的位置,插入下面这个语句:
- #if DEBUG
- zz.z(new StackTrace(new StackFrame(true)).GetFrame(0));
- #endif
或者带重要信息(跟踪 begin end 的数据变化):
- #if DEBUG
- zz.z(new StackTrace(new StackFrame(true)).GetFrame(0), begin + ", " + end);
- #endif
完整的代码示例:
- ...
- using Legalsoft.Truffer;
-
-
- namespace Legalsoft.DeepConfiser
- {
- public class TokenScanner
- {
- ///
- /// 将全部令牌(tokens)
- /// 按 {} 粗略划分到的层次型块(block)
- /// (深度优先遍历的递归算法)
- ///
- ///
- ///
- ///
- ///
- public void Segmentation(Block parent, List
tokens, int begin, int end ) - {
- #if DEBUG
- zz.z(new StackTrace(new StackFrame(true)).GetFrame(0), begin + ", " + end);
- #endif
-
- if (tokens[begin].Buffer == "{" && tokens[end].Buffer == "}")
- {
- begin++;
- end--;
- }
-
- ...
- }
- }
- }
编译选项 #if DEBUG ,仅仅适用于调试模式的编译结果;对于 RELEASE 的编译版本而言,其中的 exe 文件是不含有 探针的,也不会输出 Z-00000000.html 文件,因而,一般来说,你无需刻意去删除(撤销)前面加入的探针代码。
小药治大病。