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

汽车工厂视频网站建设/自媒体135网站免费下载安装

汽车工厂视频网站建设,自媒体135网站免费下载安装,焦作做微信网站多少钱,网站引导视频怎么做系列文章 IntelliJ IDE 插件开发 |(一)快速入门IntelliJ IDE 插件开发 |(二)UI 界面与数据持久化IntelliJ IDE 插件开发 |(三)消息通知与事件监听IntelliJ IDE 插件开发 |(四)开发一…

系列文章

  • IntelliJ IDE 插件开发 |(一)快速入门
  • IntelliJ IDE 插件开发 |(二)UI 界面与数据持久化
  • IntelliJ IDE 插件开发 |(三)消息通知与事件监听
  • IntelliJ IDE 插件开发 |(四)开发一个时间管理大师插件

前言

在上篇文章的结尾提到本文将参考 VS Code 中 TimerMaster 插件的实现效果来实现一个在 IDEA 中统计编码情况的插件,TimerMaster 的效果如下:

image-20231225092034655

image-20231225092049204

本文最终实现的插件效果如下(增加了 CV 操作和代码提交的统计😎):

image-20231228112248383

Clip_20231225_092825

由于本文涉及到大部分知识都是在前几篇文章中介绍过的,因此本文只对关键功能点和实现方式进行介绍,一些基础知识和配置就不再讲解,该插件的完整代码已上传到GitHub。

实现思路

在开发前,正如前言中提到的,是参考 TimerMaster 的实现效果。因此首先确认了要实现的功能如下:

  1. 可以统计当天、昨天、过去七天和每天平均的代码活动。
  2. 统计项包括编辑器使用/活跃时间、添加/删除的代码行数、总的键入数、扩展增加了 CV(懂得都懂) 和代码提交活动的统计。
  3. 通过点击右键菜单项后在控制台进行展示。

对于第一点,主要通过本地持久化数据进行实现,当天数据和历史数据分别进行存储(选择了 JSON 格式进行存储),然后按需取用进行统计即可。

对于第二点,涉及的功能较多,这里分开进行介绍:

  • 编辑器使用/活跃时间

    增加一个对项目生命周期的监听器,在启动事件中注册一个定时任务,每隔 n 秒(可配置)将使用时间增加 n 秒。增加一个对文档和游标位置的监听器,只要有文档操作或者游标的移动都认为是活跃状态,然后更新最新操作时间,在定时任务中会将当前时间和最新操作时间进行比较,如果不大于 m 秒(可配置)就认为编辑器处于活跃状态,并将活跃时间增加 n 秒。

  • 添加/删除的代码行数、总的键入数

    通过增加对文档的监听器,根据变更内容中的换行数目得到添加/删除的代码行数,同时根据变更内容长度是否为 1 或者为空白符来决定是否增加键入数。

  • CV 统计

    增加一个对复制粘贴事件的监听器,分别在复制和粘贴事件中增加相应的次数。

  • 代码提交统计

    监听自带 GIT 插件提供的 PUSH 事件监听器,在提交完成事件中增加对提交提交次数的统计。

对于第三点,只需要增加一个 action 并绑定到右键菜单,然后通过 ToolWindowFactory 获取到控制台视图来输出计算得到的结果即可。

根据个人需求,统计每天最早/最晚运行时间、活跃时间段、数据云存储等也很容易实现,本文就不进行拓展了。

代码实现及讲解

配置界面

在实现思路中提到了定时器间隔和代码活跃时间间隔是可配置的,由于界面比较简单,这里使用了 Kotlin UI DSL 的方式(参考第二篇文章使用 Swing UI 的拖拽方式也很容易实现),对应代码及效果如下:

// TimerMasterConfig 中部分代码private var panel = panel { row("更新间隔(秒): ") {intTextField().bindIntText(model::updateInterval).comment("<icon src='AllIcons.General.Information'>&nbsp;不设置或者小于 10, 最终都为 10.")}row("活跃间隔(秒): ") {intTextField().bindIntText(model::activeInterval).comment("<icon src='AllIcons.General.Information'>&nbsp;不设置或者小于 30, 最终都为 30.")}
}

image-20231228124547932

存储方式及格式

根据要存储的数据,定义了一个统计数据类,并增加了一个创建时间便于归档:

data class StatisticsData(var runTime: Long = 0,var activeTime: Long = 0,var keyCount: Long = 0,var addLineCount: Long = 0,var removeLineCount: Long = 0,var copyCount: Long = 0,var pasteCount: Long = 0,var pushCount: Long = 0,var createDate: String = Utils.getTodayYmd()
)

同时为了便于解析和处理,在进行本地持久化的时候,使用了 GSON 库来进行数据的 JSON (反)序列化操作。

编辑器运行/活跃时间

在前文中提到这里是通过在项目启动事件中增加定时任务来实现的,先展示代码:

class ProjectStartListener: ProjectActivity, Disposable {private val state = TimerMasterState.getInstance()override suspend fun execute(project: Project) {while (true) {delay(TimeUnit.SECONDS.toMillis(state.updateInterval.toLong()))run {// 避免多个项目运行时间统计多次, 增加满足以下规则才进行时间统计: // 当前项目与配置信息一致, 或配置信息为空, 或配置信息内的项目不处于打开状态val projectPath = project.locationHashval firstOrNull = ProjectManager.getInstance().openProjects.firstOrNull { it.locationHash == state.runProjectPath }// 判断编辑器是否处于活跃状态val active = (System.currentTimeMillis() - state.activeTime) / 1000 <= state.activeIntervalstate.runProjectPath.takeIf { it == projectPath || it.isBlank() || firstOrNull == null }?.let { state.runProjectPath = projectPathval data = Utils.parse(state.statisticsData, TypeToken.get(StatisticsData::class.java))if (data.createDate == Utils.getTodayYmd()) {// 计算运行和活跃时间data.runTime += state.updateIntervalif (active) {data.activeTime += state.updateInterval}state.statisticsData = Utils.stringify(data)} else {// 存储的如果不是当日的数据, 则将数据加入到历史数据, 然后再初始化数据val arr = Utils.parse(state.historyData, object : TypeToken<MutableList<String>>() {})arr.add(state.statisticsData)state.historyData = Utils.stringify(arr)val newData = StatisticsData()newData.runTime = state.updateInterval.toLong()if (active) {newData.activeTime = state.updateInterval.toLong()}state.statisticsData = Utils.stringify(newData)}}}}}override fun dispose() {}}

增加以下配置:

<extensions defaultExtensionNs="com.intellij"><postStartupActivityimplementation="cn.butterfly.timermaster.listener.ProjectStartListener"/>
</extensions>

这里需要注意的是我们可能会同时打开多个项目,因此为了避免运行和活跃时间被多次计算,这里通过使用 runProjectPath 来存储当前项目路径的 hash 值,只有该值为空(暂无运行的项目)或者该值不空但打开的项目中没有等于该值的项目(项目打开后又关闭)时才会更新该值为当前运行项目的路径 hash 值。相应地,也只有满足以上条件才会统计运行和活跃时间,同时根据 createDate 字段来决定是在当日数据上进行累积,还是归档该数据然后初始化今日数据。

添加/删除的代码行数、总的键入数

这里是在编辑器创建事件中,对相应的文档和光标移动增加监听器来统计添加/删除的代码行数、总的键入数,此外还会更新编辑器活跃时间(Utils.initData() 中进行记录),代码如下:

class EditorListener: EditorFactoryListener, BulkAwareDocumentListener, CaretListener {private val state = TimerMasterState.getInstance()private val fileSet = mutableSetOf<String>()override fun editorCreated(event: EditorFactoryEvent) {// 避免重复增加监听器val file = FileDocumentManager.getInstance().getFile(event.editor.document) ?: returnif (file.path in fileSet) {return}fileSet.add(file.path)// 监听编辑操作event.editor.document.addDocumentListener(this)// 监听光标移动事件event.editor.caretModel.addCaretListener(this)}override fun documentChangedNonBulk(event: DocumentEvent) {val data = Utils.initData()event.takeIf { (it.oldFragment.isNotEmpty() or it.newFragment.isNotEmpty()) or !it.isWholeTextReplaced }?.let {// 只对字符长度为 1 和非空空白符的情况进行统计if (it.newFragment.isNotEmpty() && (it.newFragment.length == 1 || it.newFragment.trim().isEmpty())) {++data.keyCount}// 根据文档代码段变更信息判断是新增还是删除行if (it.oldFragment.contains('\n')) {data.removeLineCount += it.oldFragment.count { item -> item == '\n' }}if (it.newFragment.contains('\n')) {data.addLineCount += it.newFragment.count { item -> item == '\n' }}}state.statisticsData = Utils.stringify(data)}override fun caretPositionChanged(event: CaretEvent) {state.statisticsData = Utils.stringify(Utils.initData())}}

增加以下配置:

<extensions defaultExtensionNs="com.intellij"><editorFactoryListener implementation="cn.butterfly.timermaster.listener.EditorListener"/>
</extensions>

结合注释,代码不难理解,这里不再介绍。

CV 统计

关于复制粘贴的统计也很简单,官方给我们提供了相应的扩展点,我们增加相应的实现即可:

class CopyPasteListener: CopyPastePreProcessor {private val state = TimerMasterState.getInstance()override fun preprocessOnCopy(p0: PsiFile?, p1: IntArray?, p2: IntArray?, p3: String?): String? {val data = Utils.initData()++data.copyCountstate.statisticsData = Utils.stringify(data)return null}override fun preprocessOnPaste(p0: Project?, p1: PsiFile?, p2: Editor?, text: String?, p4: RawText?): String {val data = Utils.initData()++data.pasteCountstate.statisticsData = Utils.stringify(data)return text ?: ""}}

增加以下配置:

<extensions defaultExtensionNs="com.intellij"><copyPastePreProcessorimplementation="cn.butterfly.timermaster.listener.CopyPasteListener"/>
</extensions>

而如何知道官方给我们提供了哪些扩展点,在上一篇文章中也说明查看官方文档即可,例如复制粘贴的扩展点:

image-20231228132128049

代码提交统计

这里使用的GitPushListener也是在上述官方文档中提供的:

image-20231228132534460

使用方式也很简单:

class GitListener: GitPushListener {private val state = TimerMasterState.getInstance()override fun onCompleted(repository: GitRepository, pushResult: GitPushRepoResult) {val data = Utils.initData()++data.pushCountstate.statisticsData = Utils.stringify(data)}}

增加以下配置:

<depends>Git4Idea</depends><projectListeners><listener class="cn.butterfly.timermaster.listener.GitListener"topic="git4idea.push.GitPushListener"/>
</projectListeners>

build.gradle.kts中也需要增加以下配置:

intellij {// 用到的插件plugins.set(listOf("Git4Idea"))
}

由于GitPushListener是自带 GIT 插件所提供的监听器,因此还增加了第一行,其中Git4Idea是 GIT 的插件 id:

image-20231228132835555

展示报告

有了以上获取和存储数据的基础,这里就很简单了,关于得到展示数据的方式这里就不介绍了,大家可以去查看TimerMasterOutputAction的源码,这里主要介绍如何在控制台进行报告的输出。

其实控制台视图也可以看作是第二篇文章中提到的侧边栏,因此也需要继承实现ToolWindowFactory

class ConsoleWindow: ToolWindowFactory {override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {if (Utils.getConsoleViews()[project] == null) {Utils.createToolWindow(project, toolWindow)}Utils.toolWindows[project] = toolWindow}}// Utils 中的方法
fun createToolWindow(project: Project, toolWindow: ToolWindow) {val consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(project).consoleconsoleViews[project] = consoleViewval content = toolWindow.contentManager.factory.createContent(consoleView.component, "TimerMaster Output", false)content.component.isVisible = truecontent.isCloseable = truetoolWindow.contentManager.addContent(content)
}// 在控制台中显示信息
fun consoleInfo(project: Project, msg: String) {if (consoleViews[project] == null) {ToolWindowManager.getInstance(project).getToolWindow("TimerMaster Console")?.let { createToolWindow(project, it) }}consoleViews[project]?.clear()consoleViews[project]?.print(msg, ConsoleViewContentType.NORMAL_OUTPUT)// 显示控制台窗口, 减去手动点击侧边栏按钮的操作toolWindows[project]?.activate(null, false)
}

这里需要注意的是由于可能多个项目都打开了该控制台视图,因此需要使用一个 map 继续保存,避免内容错乱。

最后以一个动图结尾:

动画

总结

本文通过实现一个对代码活动统计的插件算是对前几篇文章的一个总结,目前只在本地运行测试了几天,可能有一些不妥的实现方式和 BUG,欢迎一起交流讨论。另外截止到本篇,关于 IntelliJ 平台插件开发的基础知识也算告一段落,后续文章则优先讲解关于虚拟文件、PSI和编辑器操作相关的知识,而关于国际化、主题插件开发等则看情况夹杂在其中进行讲解。

相关文章:

IntelliJ IDE 插件开发 | (四)开发一个时间管理大师插件

系列文章 IntelliJ IDE 插件开发 |&#xff08;一&#xff09;快速入门IntelliJ IDE 插件开发 |&#xff08;二&#xff09;UI 界面与数据持久化IntelliJ IDE 插件开发 |&#xff08;三&#xff09;消息通知与事件监听IntelliJ IDE 插件开发 |&#xff08;四&#xff09;开发一…...

【ChatGPT 默认强化学习策略】PPO 近端策略优化算法

PPO 近端策略优化算法 PPO 概率比率裁剪 演员-评论家算法演员-评论家算法&#xff1a;多智能体强化学习核心框架概率比率裁剪&#xff1a;逐步进行变化的方法PPO 目标函数的设计重要性采样KL散度 PPO 概率比率裁剪 演员-评论家算法 论文链接&#xff1a;https://arxiv.org…...

【银行测试】金融银行-理财项目面试/分析总结(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 银行理财相关的项…...

张江智荟毁约offer

毕业8年后&#xff0c;找工作被国企歧视学历&#xff01;已经收到了offer&#xff0c;在入职前一周被通知要撤回offer&#xff0c;拒绝录用&#xff0c;理由居然是他们只要本科211以上的人 这是我今天&#xff08;2023-12-26&#xff09;亲身经历的事&#xff0c;听说过面试前…...

ubuntu 系统终端颜色设置

1 开启终端颜色 # 第一步&#xff1a; 在 ~/.bashrc 中设置 force_color_promptyes# 第二步&#xff1a; 执行 source ~/.bashrc2 对于精减的 .bashrc 在 ~/.bashrc 中添加以下内容&#xff0c;再执行 source ~/.bashrc &#xff1a; # uncomment for a colored prompt, if…...

【Vue】class与style绑定

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…...

大厂前端面试题总结(百度、字节跳动、腾讯、小米.....),附上热乎面试经验!

先简单介绍下自己&#xff0c;我“平平无奇小天才”一枚&#xff0c;毕业于南方普通985普通学生&#xff0c;有幸去百度、字节面试&#xff0c;感觉大公司就是不一样&#xff0c;印象最深的是字节&#xff0c;所以有必要总结一下面试经验&#xff0c;以及面试中遇到的一些问题&…...

EXPLORING DIFFUSION MODELS FOR UNSUPERVISED VIDEO ANOMALY DETECTION 论文阅读

EXPLORING DIFFUSION MODELS FOR UNSUPERVISED VIDEO ANOMALY DETECTION 论文阅读 ABSTRACT1. INTRODUCTION2. RELATEDWORK3. METHOD4. EXPERIMENTAL ANALYSIS AND RESULTS4.1. Comparisons with State-Of-The-Art (SOTA)4.2. Diffusion Model Analysis4.3. Qualitative Result…...

当 ML 遇到 DevOps:如何理解 MLOps

近年来&#xff0c;人工智能 &#xff08;AI&#xff09; 和机器学习 &#xff08;ML&#xff09; 已经席卷全球&#xff0c;几乎成为任何行业的重要组成部分&#xff0c;从零售和娱乐到医疗保健和银行业。这些技术能够通过分析大量数据实现运营自动化、降低成本和促进决策&…...

vue+element+springboot实现多张图片上传

1.需求说明 2.实现思路 3.el-upload组件主要属性说明 4.前端传递MultipartFile数组与服务端接收说明 5.完整代码 1.需求说明 动态模块新增添加动态功能,支持多张图片上传.实现过程中对el-upload组件不是很熟悉,踩了很多坑,当然也参考过别的文章,发现处理很…...

react使用useState更新数组失败

失败案例&#xff1a; const [addBox, setAddBox] useState([])const itemAdd (item) >{addBox.push(item);setAddBox(addBox)console.log(addBox,点击添加按钮)} 原因&#xff1a;react的useState hook监听的是浅监听 在 React 中&#xff0c;使用 useState Hook 来更新…...

《LIO-SAM阅读笔记》3.后端优化

前言&#xff1a; LIO-SAM后端优化部分写在了mapOptimization.cpp文件中&#xff0c;本部分主要进行了激光帧的scan-to-map匹配&#xff0c;回环检测以及关键帧的因子图优化。本部分主要有两个环节同步进行&#xff0c;一个单独开辟了回环检测线程&#xff0c;另外一个是lidar…...

mac下jd-gui提示没有找到合适的jdk版本

mac下jd-gui提示jdk有问题 背景解决看一下是不是真有问题了方法一&#xff1a;修改启动脚本方法二&#xff1a;设置launchd环境变量 扩展动态切jdk脚本(.bash_profile) 背景 配置了动态jdk后&#xff0c;再次使用JD-GUI提示没有找到合适的jdk版本。 解决 看一下是不是真有问题…...

FlinkSQL窗口实例分析

Windowing TVFs Windowing table-valued functions (Windowing TVFs)&#xff0c;即窗口表值函数 注意&#xff1a;窗口函数不可以单独使用&#xff0c;需要聚合函数&#xff0c;按照 window_start、window_end 分区&#xff0c;即存在&#xff1a;group by window_start,wind…...

18-网络安全框架及模型-信息系统安全保障模型

信息系统安全保障模型 1 基本概念 信息系统安全保障是针对信息系统在运行环境中所面临的各种风险&#xff0c;制定信息系统安全保障策略&#xff0c;设计并实现信息系统安全保障架构或模型&#xff0c;采取工程、技术、管理等安全保障要素&#xff0c;将风险减少至预定可接受的…...

Android 提取(备份)apk(安装包)

Android 提取(备份)apk(安装包) 一、通过安卓代码的方式 主要分三步&#xff1a; 根据应用找到包名根据包名获得apk提取apk 提取apk代码 private static final String BACKUP_PATH "/sdcard/backup1/"; private static final String APK ".apk";pri…...

gRPC-Go基础(4)metadata和超时设置

文章目录 0. 简介1. metadata1.1 metadata结构1.2 metadata创建1.3 客户端处理metadata1.4 服务端处理metadata1.5 metadata的传输 2. 超时设置2.1 客户端输出超时信息2.2 服务端端接收超时信息 3. 小结 0. 简介 Go在多个go routine之间传递数据使用的是Go SDK提供的context包…...

语言模型:从n-gram到神经网络的演进

目录 1 前言2 语言模型的两个任务2.1 自然语言理解2.2 自然语言生成 3 n-gram模型4 神经网络语言模型5 结语 1 前言 语言模型是自然语言处理领域中的关键技术之一&#xff0c;它致力于理解和生成人类语言。从最初的n-gram模型到如今基于神经网络的深度学习模型&#xff0c;语言…...

docker compose 部署 grafana + loki + vector 监控kafka消息

Centos7 随笔记录记录 docker compose 统一管理 granfana loki vector 监控kafka 信息。 当然如果仅仅是想通过 Grafana 监控kafka&#xff0c;推荐使用 Grafana Prometheus 通过JMX监控kafka 目录 1. 目录结构 2. 前提已安装Docker-Compose 3. docker-compose 自定义服…...

kubeadm创建k8s集群

kubeadm来快速的搭建一个k8s集群&#xff1a; 二进制搭建适合大集群&#xff0c;50台以上。 kubeadm更适合中下企业的业务集群。 部署框架 master192.168.10.10dockerkubelet kubeadm kubectl flannelnode1192.168.10.20dockerkubelet kubeadm kubectl flannelnode2192.168.1…...

鸿蒙开发之android对比开发《基础知识》

基于华为鸿蒙未来可能不再兼容android应用&#xff0c;推出鸿蒙开发系列文档&#xff0c;帮助android开发人员快速上手鸿蒙应用开发。 1. 鸿蒙使用什么基础语言开发&#xff1f; ArkTS是鸿蒙生态的应用开发语言。它在保持TypeScript&#xff08;简称TS&#xff09;基本语法风…...

2702 高级打字机

因为Undo操作只能撤销Type操作&#xff0c;所以Undo x 实际上就是删除文章末尾x个字母。用一个栈即可解决&#xff08;每个字母最多进出一次&#xff09;。 这种情况下只需要设计一个合理的数据结构依次执行操作即可。 版本树&#xff1a;Undo x撤销最近的x次修改操作&#xf…...

yolov5旋转目标检测-遥感图像检测-无人机旋转目标检测-附代码和原理

综述 为了解决旋转目标检测问题&#xff0c;研究者们提出了多种方法和算法。以下是一些常见的旋转目标检测方法&#xff1a; 基于滑动窗口的方法&#xff1a;在图像上以不同的尺度和角度滑动窗口&#xff0c;通过分类器判断窗口中是否存在目标。这种方法简单直观&#xff0c;…...

Qt学习:Qt的意义安装Qt

Qt 的简介 QT 是一个跨平台的 C图形用户界面应用程序框架。它为程序开发者提供图形界面所需的所有功能。它是完全面向对象的&#xff0c;很容易扩展&#xff0c;并且允许真正地组件编程。 支持平台 xP 、 Vista、Win7、win8、win2008、win10Windows . Unix/Linux: Ubuntu 等…...

Anylogic Pro 8.8.x for Mac / for Linux Crack

Digital twins – a step towards a digital enterprise AnyLogic是唯一一个支持创建模拟模型的方法的模拟建模工具&#xff1a;面向过程&#xff08;离散事件&#xff09;、系统动态和代理&#xff0c;以及它们的任何组合。AnyLogic提供的建模语言的独特性、灵活性和强大性使…...

ROS无人机初始化GPS定位漂移误差,确保无人机稳定飞行

引言&#xff1a; 由于GPS在室外漂移的误差比较大&#xff0c;在长时间静止后启动&#xff0c;程序发布的位置可能已经和预期的位置相差较大&#xff0c;导致无法完成任务&#xff0c;尤其是气压计的数据不准&#xff0c;可能会导致无人机不能起飞或者一飞冲天。本文主要是在进…...

k8s网络类型

k8s中的通信模式&#xff1a; pod内部之间容器与容器之间的通信。 在同一个pod中的容器共享资源和网络&#xff0c;使用同一个网络命名空间。可以直接通信的。 同一个node节点之内&#xff0c;不同pod之间的通信。 每一个pod都有一个全局的真实的IP地址&#xff0c;同一个n…...

Seata 中封装了四种分布式事务模式,分别是: AT 模式, TCC 模式, Saga 模式, XA 模式,

文章目录 seata概述Seata 中封装了四种分布式事务模式&#xff0c;分别是&#xff1a;AT 模式&#xff0c;TCC 模式&#xff0c;Saga 模式&#xff0c;XA 模式&#xff0c; 今天我们来聊聊seata seata 概述 在微服务架构下&#xff0c;由于数据库和应用服务的拆分&#xff0c…...

为什么设计制造行业需要数据加密?

设计制造行业是一个涉及多种技术、工艺、材料和产品的广泛领域&#xff0c;它对经济和社会的发展有着重要的影响。然而&#xff0c;随着数字化、智能化和网络化的发展&#xff0c;设计制造行业也面临着越来越多的数据安全风险&#xff0c;如数据泄露、数据篡改、数据窃取等。这…...

查看ios app运行日志

摘要 本文介绍了一款名为克魔助手的iOS应用日志查看工具&#xff0c;该工具可以方便地查看iPhone设备上应用和系统运行时的实时日志和奔溃日志。同时还提供了奔溃日志分析查看模块&#xff0c;可以对苹果奔溃日志进行符号化、格式化和分析&#xff0c;极大地简化了开发者的调试…...