当前位置: 首页 > article >正文

《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——架构

前文:
《leetcode-runner》如何手搓一个debug调试器——引言

文章目录

  • 设计引入
  • 为什么这么设计
  • 存在难点
    • 1. 环境准备
    • 2. 调试程序

仓库地址:leetcode-runner

本文主要聚焦leetcode-runner对于debug功能的整体设计,并讲述设计原因以及存在的难点

设计引入

让我们来思考一下,一个最简单的调试器需要哪些内容

首先,它能够接受用户的输入

其次,它能够读懂用户想让调试器干嘛,并做出相应的行为

最后,执行完操作后需要显示结果

基于此,我们可以给出一个最简单的设计

在这里插入图片描述

  • Reader:负责读取用户的输入,识别输入并转化为系统能够识别的信息
  • Executor:根据Reader解读分析得到的数据,执行具体的调试行为,产生执行结果
  • Output:根据执行结果,进行可视化显示

这个最简单的设计看似还凑活,实际上确实只能凑活,且不论用户该输入啥,单说Reader,哥们你该解析出个啥呢?解析啥才能让executor识别,并执行对应逻辑

因此,我们还需要引入一套非常重要的系统——指令系统

在这里插入图片描述

当引入指令系统后,整个debug模块都被盘活了。debug的每个模块都能识别指令系统,只要遇到某条指令,就会执行对应的操作

而reader就负责将用户输入的命令行字符串解析转换为指令,然后系统就会依照指令执行相应逻辑

有了指令系统,debug项目完整了吗?No No No

让我们将视线聚焦到executor上,既然是调试代码,那么被调试的代码有被运行吗?被谁运行了呢?在我们目前的架构中是不存在相应的模块。也就是说,我们缺少了调试服务,因此进一步完善,得到如下架构图

在这里插入图片描述

调试服务运行被调试的代码,然后获取得到被调试代码相应信息

executor服务负责与调试服务沟通,通知调试服务执行相应逻辑;调试服务将数据信息存储,交由executor取用,获取目标代码相关信息

接下来,再让我们结合具体场景思考

现在,用户在使用leetcode-runner,编写了一个Solution的class,然后他想要使用debug调试功能,请问,我们能直接调试Solution代码吗?

调试个屁!要是能这么轻松,我还写个屁的调试器!

首先,项目需要为用户自动编写一个Main类,也就是入口函数。此外,还需要将测试案例转换成对应代码,比如’[1,2,3]',转换成Java代码就是int[] a = new int[] {1,2,3};,然后实例化Solution class,调用相应的方法,同时将测试案例转换得到的变量传入方法内,这才算完成前置工作

像这类前置工作的准备,leetcode-runner统一将其封装在DebugEnv对象内,DebugEnv提供的prepare()方法就是为了完成前置工作

进一步完善,可以得到如下架构图

在这里插入图片描述

现在,整个系统像样了不少,但如果想要暴露给外部系统使用,还是麻烦了点,如果想要别人使用方便,我们该怎么办呢?答案是——封装!

现在,我们引入Debugger类,负责启动整个debug框架,得到如下的架构图

在这里插入图片描述

接下来进一步完善细节,将leetcode-runner负责各个模块的核心类名填入系统,得到如下架构图

请添加图片描述

为什么这么设计

以笔者粗俗的理解,设计的目的是为了更好的编写代码。一个好的设计可以避免出现非常对的bug,在一定程度上提高编程的速度

让我们回看上方做出的设计,我们不难发现,所有的功能都被封装在独立的模块之内。模块与模块之间并没有过强的耦合,不会出现牵一发而动全身的情况

另外,如此设计还有一个好处——不依赖具体的语言。啥意思呢?打个比方,我现在要做Java的debug调试器,我需要实现的子类有DebugEnvDebuggerExecuteContext调试服务InstExecutor。其他的模块内容完全可以复用,比如InstReaderOutput,指令系统…因为这些模块不依赖于具体的语言

当我需要实现一个python的debug调试器,同样可以复用这些模块。此外,DebugEnv,Debugger也存在可以复用逻辑。

DebugEnv,不同语言总有相同的准备活动,比如可执行程序(jdk,python解释器…)测试案例准备(程序输入)代码创建(Main函数,也就是程序入口)…因为有如此多的共用逻辑,完全可以提取到父类当中,在leetcode-runner中,就提供了AbstractDebugEnv封装公用逻辑

Debugger,作为debug框架入口,如读取执行可视化这一套流程,就可以复用

存在难点

1. 环境准备

在环境准备的过程中,我们最需要关注的是Main函数的创建,这里给一个leetcode-runner创建的Main函数,以leetcode-1367题目为例。leetcode-runner根据该题提供的Solution代码片段创建相应的Main函数

Solution代码片段

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSubPath(ListNode head, TreeNode root) {
        
    }
}

插件项目自动生成的Main文件

import java.util.*;
/*

*/
public class Main {
    public static void main(String[] args) {
        Solution solution = new Solution();
        String bxdnslspts = "[4,2,8]";
        bxdnslspts = bxdnslspts.trim();

        ListNode a0 = null;
        if (!"[]".equals(bxdnslspts)) {
            // 把collect变为数组
            Integer[] split =
                    Arrays.stream(
                                    bxdnslspts.replace("[", "")
                                            .replace("]", "")
                                            .split(","))
                            .map(Integer::parseInt)
                            .toArray(Integer[]::new);
            int i = 0;
            a0 = new ListNode(split[i]);
            ListNode cp = a0;
            i += 1;
            // 迭代
            for (; i < split.length; ++i) {
                cp.next = new ListNode(split[i]);
                cp = cp.next;
            }
        }
        String cnuepugbsq = "[1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]";
        cnuepugbsq = cnuepugbsq.trim();

        TreeNode a1 = null;
        if (!"[]".equals(cnuepugbsq)) {
            // 把collect变为数组
            Integer[] split =
                    Arrays.stream(
                                    cnuepugbsq.replace("[", "")
                                            .replace("]", "")
                                            .split(","))
                            .map(e -> "null".equals(e) ? null : (int) Integer.parseInt(e))
                            .toArray(Integer[]::new);
            int i = 0;
            a1 = new TreeNode(split[i]);
            i++;
            Queue<TreeNode> q = new LinkedList<>();
            q.add(a1);

            while (!q.isEmpty()) {
                TreeNode node = q.poll();
                // 添加它的左节点
                if (i < split.length) {
                    if (split[i] != null) {
                        node.left = new TreeNode(split[i]);
                        q.add(node.left);
                    }
                    i += 1;
                }
                // 添加右节点
                if (i < split.length) {
                    if (split[i] != null) {
                        node.right = new TreeNode(split[i]);
                        q.add(node.right);
                    }
                    i += 1;
                }
            }
        }
        solution.isSubPath(a0, a1);

    }
}

这里的难点是,如何根据Solution的核心代码片段,创建对应的Main函数。此外,还需要通过测试案例,转换为相应的代码

2. 调试程序

调试程序,这部分与语言强相关,并且非常底层。如果你是Java的调试器开发,你将明白Java在底层到底封装了多少内容,提供了多大的便利。光是自动拆箱自动装箱,在调试程序编写时都需要手动处理判断,巨tm麻烦

另外,调试程序的逻辑处理,执行顺序,以及最复杂的表达式计算,这些都是调试程序编写的难点


http://www.kler.cn/a/509726.html

相关文章:

  • 【大数据2025】Hadoop 万字讲解
  • MySQL面试题2025 每日20道
  • AUTOSAR从入门到精通-无人驾驶网约车(Robotaxi)
  • 重学SpringBoot3-Spring Retry实践
  • Titans 架构中的记忆整合:Memory as a Context;Gated Memory;Memory as a Layer
  • MySQL 事务
  • Web自动化:Cypress 测试框架概述
  • 自己造轮子-基于Ceres的GNSS-INS松耦合组合导航算法
  • 为AI聊天工具添加一个知识系统 之51 从形态学简约到纯粹的思维再映射到AI操作系统 之2
  • liunx进程函数汇总(包含管道、共享内存、消息队列、信号)
  • 浅谈计算机网络03 | 现代网络组成
  • 【tailscale 和 ssh】当服务器建立好节点,但通过客户端无法通过 ssh 连接
  • PostgreSQL-WAL日志介绍(一)
  • 蓝桥杯训练—矩形面积交
  • 服务器怎样防范网络蠕虫病毒?
  • Ubuntu 手动安装 Open WebUI 完整指南
  • 《CPython Internals》阅读笔记:p221-p231
  • 【优化算法】狭义相对论搜索算法:一种基于狭义相对论物理学的元启发式方法
  • 《内网穿透:开启网络世界的任意门》
  • 于灵动的变量变幻间:函数与计算逻辑的浪漫交织(下)
  • 20250118拿掉荣品pro-rk3566开发板上Android13下在uboot和kernel启动阶段的Rockchip这个LOGO标识
  • 深入浅出JSON:数据交换的轻量级解决方案
  • OpenVela——专为AIoT领域打造的开源操作系统
  • SpringBoot3 升级介绍
  • Hexo + NexT + Github搭建个人博客
  • 解决:Loading class `com.mysql.jdbc.Driver‘. This is deprecated