• C#,入门教程——程序运行时的调试技巧与逻辑错误探针技术与源代码


    一、关于程序错误的概述

    编程的水平,如同人之体质,主要来自于基础,数学与算法;

    但编程的功力,如同人的武功或运动成绩,则主要来自于调试、测试与跟踪。

    程序的错误大致可分为:语法错误数据错误逻辑错误

    我们都知道,如果程序在哪行?哪列出现语法错误,Visual Studio 编译器会直接指出来,一瞅便知,非常方便。

    编译器首先就是为解决语法错误而设计的工具。

    数据错误主要出现于局部,或 接口部分。局部性的数据错误,通过设置断点,然后按 F10, F11 等快捷键的单步调试是不错的办法。

    但很多无法进行单步调试的 接口型数据错误,是比较麻烦的。只能用下面的办法。 

    逻辑错误是致命的、也是最难跟踪与分析的。逻辑错误还是过程性错误,往往要在程序运行时才会出现。可是,如果是编译通过的程序,称为运行时代码,出现错误,怎么知道哪行?哪列?哪个函数出现问题呢?

    你可能阅读了很多这方面的文章,但就 C# 而言,建议你使用 北京联高软件开发有限公司 在这里开源的 Truffer 的基础类之一:C#运行时逻辑错误探针类

    特点:

    (1)直接输出哪个源代码?行号?列号?函数名?重点信息;

    (2)插入 探针 与 撤销 都非常简单;

    (3)可 分页 输出超过 20 亿条的探针信息,只要你的硬盘空间足够富裕;

    (4)基于输出的 探针信息,可以做很多的分析(大数据);

    (5)基于下面代码的Truffer Xtracer功能更多,比如可以检测 死循环,可以探测堆、栈及重要数据的超界等。

    二、逻辑错误探针类(简版)

    1. #if DEBUG
    2. using System;
    3. using System.IO;
    4. using System.Text;
    5. using System.Collections;
    6. using System.Collections.Generic;
    7. using System.Diagnostics;
    8. namespace Legalsoft.Truffer
    9. {
    10. public static class zz
    11. {
    12. private static StringBuilder sb { get; set; } = new StringBuilder();
    13. private static string head { get; set; } = "";
    14. private static string tail { get; set; } = "";
    15. private static string format { get; set; } = "";
    16. ///
    17. /// 跟踪器的步数
    18. ///
    19. private static long step { get; set; } = 0;
    20. ///
    21. /// 每个 html 文件显示的 step 数
    22. ///
    23. private static long pagesize { get; set; } = 5000;
    24. ///
    25. /// 每隔 writesize 输出跟踪器语句(html)
    26. /// 如果设置 writesize=0,则每个跟踪器语句都输出文件;
    27. ///
    28. private static long writesize { get; set; } = 100;
    29. ///
    30. /// 初始化
    31. ///
    32. private static void a()
    33. {
    34. if (head.Length == 0)
    35. {
    36. StringBuilder sx = new StringBuilder();
    37. sx.AppendLine("");
    38. sx.AppendLine("");
    39. sx.AppendLine("");
    40. sx.AppendLine("");
    41. sx.Append("
    42. ");
    43. sx.Append("
    44. ");
    45. sx.Append("
    46. ");
    47. sx.Append("
    48. ");
    49. sx.Append("
    50. ");
    51. sx.Append("
    52. ");
    53. sx.Append("
    54. ");
    55. sx.AppendLine("
    56. ");
    57. head = sx.ToString();
    58. }
    59. if (tail.Length == 0)
    60. {
    61. StringBuilder sx = new StringBuilder();
    62. sx.Append("
    63. ");
    64. sx.Append("
    65. ");
    66. sx.Append("
    67. ");
    68. sx.Append("
    69. ");
    70. sx.Append("
    71. ");
    72. sx.Append("
    73. ");
    74. sx.Append("
    75. ");
    76. sx.AppendLine("
    77. ");
    78. sx.AppendLine("
    79. 序号文件名函数名信息
      序号文件名函数名信息
      "
      );
    80. sx.AppendLine("");
    81. sx.AppendLine("");
    82. tail = sx.ToString();
    83. }
    84. if (format.Length == 0)
    85. {
    86. StringBuilder sx = new StringBuilder();
    87. sx.Append("");
    88. sx.Append("{0}");
    89. sx.Append("{1}");
    90. sx.Append("{2}");
    91. sx.Append("{3}");
    92. sx.Append("{4}");
    93. sx.Append("{5}");
    94. sx.AppendLine("");
    95. format = sx.ToString();
    96. }
    97. }
    98. ///
    99. /// 探针函数
    100. /// 调用方法为:
    101. /// zz.z(new StackTrace(new StackFrame(true)).GetFrame(0));
    102. ///
    103. /// 堆栈帧
    104. /// 你可以定义的任何类型的输出信息
    105. public static void z(StackFrame sf, string info = "")
    106. {
    107. a();
    108. string buf = String.Format(format, step + 1, sf.GetFileName(), sf.GetFileLineNumber(), sf.GetFileColumnNumber(), sf.GetMethod().Name, info);
    109. sb.AppendLine(buf);
    110. if (step > 0)
    111. {
    112. if ((step % writesize) == 0 || writesize == 0)
    113. {
    114. string fn = String.Format("Z-{0:D8}.html", (int)((step - (step % pagesize)) / pagesize));
    115. File.WriteAllText(fn, head + sb.ToString() + tail, Encoding.UTF8);
    116. }
    117. if ((step % pagesize) == 0)
    118. {
    119. sb.Clear();
    120. sb.AppendLine(buf);
    121. }
    122. }
    123. step++;
    124. }
    125. }
    126. }
    127. #endif

    使用方法:

    请将上面的代码保存为 Basic.Z.cs ,并加入 工程项目 project 即可。

    三、探针类的使用

    在原来的程序中加入适当的探针代码,然后运行程序即可输出 Z-0000000.html 这样的页面文件,可以直接用浏览器阅读,了解程序调用的顺序,并分析可能存在的逻辑问题。

    1、探针插入

    在需要记录(探测)的位置,插入下面这个语句: 

    1. #if DEBUG
    2. zz.z(new StackTrace(new StackFrame(true)).GetFrame(0));
    3. #endif

    或者带重要信息(跟踪 begin end 的数据变化):

    1. #if DEBUG
    2. zz.z(new StackTrace(new StackFrame(true)).GetFrame(0), begin + ", " + end);
    3. #endif

    完整的代码示例:

    1. ...
    2. using Legalsoft.Truffer;
    3. namespace Legalsoft.DeepConfiser
    4. {
    5. public class TokenScanner
    6. {
    7. ///
    8. /// 将全部令牌(tokens)
    9. /// 按 {} 粗略划分到的层次型块(block)
    10. /// (深度优先遍历的递归算法)
    11. ///
    12. ///
    13. ///
    14. ///
    15. ///
    16. public void Segmentation(Block parent, List tokens, int begin, int end)
    17. {
    18. #if DEBUG
    19. zz.z(new StackTrace(new StackFrame(true)).GetFrame(0), begin + ", " + end);
    20. #endif
    21. if (tokens[begin].Buffer == "{" && tokens[end].Buffer == "}")
    22. {
    23. begin++;
    24. end--;
    25. }
    26. ...
    27. }
    28. }
    29. }

    2、探针的撤销

    编译选项 #if DEBUG ,仅仅适用于调试模式的编译结果;对于 RELEASE 的编译版本而言,其中的 exe 文件是不含有 探针的,也不会输出 Z-00000000.html 文件,因而,一般来说,你无需刻意去删除(撤销)前面加入的探针代码。

    小药治大病。

  • 相关阅读:
    无中微子双贝塔衰变
    2023年9月Web3行业月度发展报告区块链篇 | 陀螺科技会员专享
    深化校企合作|云畅科技与湖南女子学院签订校企合作协议
    从事先进计算的工程师对此都有什么感想?
    Ansible密码正确但无法登录目标服务器
    [leecode每日一题]面试题 01.09. 字符串轮转
    Linux之Python-APT 软件管理和远程登录
    【milkv】max98357a驱动添加speaker
    安防监控/视频监控系统EasyCVR平台界面侧边栏优化
    Linux虚拟化指南:构建虚拟化环境
  • 原文地址:https://blog.csdn.net/beijinghorn/article/details/126014885