DEX455

Apex 调试与异常处理完全指南

课程介绍

Apex调试与异常处理完全指南:Debug Log(数据库操作/HTTP callout/Apex错误/资源使用/工作流/20MB限制+自动缩小/24h系统+7天监控保留/15min-1000MB禁用/频繁类日志导致请求失败)、日志节(SECTION_START+END/SOQL+DML+CALLOUT+USER_DEBUG+EXCEPTION+CUMULATIVE_LIMIT)、Log Category(7类)+Level(8级)、Trace Flag(类/触发器覆盖用户级别但不触发生成/优先级)、Log Inspector(时间线+执行概览+源代码链接+筛选器)、异常(throw+try+catch+finally/未处理异常回滚+用户错误消息+自动邮件通知)、10种内置异常(DmlException/NullPointerException/QueryException/SObjectException/LimitException/CalloutException/JSONException等)、Exception通用方法(4个)、DmlException逐记录方法(getNumDml+getDmlIndex+getDmlMessage+getDmlType+getDmlFieldNames+getDmlId)、多catch块(具体到一般顺序/异常链initCause)、自定义异常(extends Exception/Exception结尾/内部类/4隐式构造函数/重新抛出+异常转换)。

课程章节

本课程共有 13 个章节

  • 1

    Debugging & Exceptions

    第 427 页

    同学们,咱们今天来聊聊Apex里一个特别实用的话题——怎么调试你的代码,以及怎么优雅地处理错误。 想象一下,你写的代码跑着跑着突然报错了,但你两眼一抹黑,完全不知道从哪里查起,是不是很崩溃?别担心,Salesforce给我们准备了一整套工具,让你能像侦探一样,把问题查个水落石出。 首先,最常用的就是,开发人员控制台,和,调试日志,。你可以在控制台里直接运行代码、查看日志。那个调试日志啊,就像飞机的黑匣子,会把你代码执行时的一举一动都记下来。比如,你做了哪些数据库操作?调用了哪些外部接口?有没有报错?系统资源用了多少?甚至自动化流程的触发情况,全都清清楚楚。你只要打开日志一看,问题就藏不住了。 另外还有个很贴心的功能叫,收件箱日志,,专门针对一些异步的操作,比如你发了个批处理任务,它在后台跑的时候,也会生成日志,方便你事后追溯。 说完查问题,我们再来看怎么,处理异常,。写代码不可能万无一失,用户可能输入奇怪的值,外部系统可能没响应。这时候你不能让程序直接崩溃,得有个预案。Apex就支持像Java那样的异常处理结构:`try`、`catch`、`finally`。你可以用`throw`主动抛出一个异常,然后在`try`块里试着跑可能有问题的代码,一旦出了问题,就跳进`catch`块里处理,比如记录一下错误详情,或者给用户一个友好的提示。而`finally`块呢,不管有没有异常都会执行,适合做收尾清理工作。 更厉害的是,你还可以创建,自定义异常类,。因为标准异常也许不够表达你业务上的特殊错误,比如“订单金额不能为负”这种,你就可以定义一个自己的异常,抛出去的时候语义就特别清晰。 最后还有一个保底机制:万一你的代码在运行时抛出了,未处理的异常,,Salesforce会自动给指定的人发,邮件通知,,这样就算你没有时刻盯着日志,也不会漏掉严重的问题。 好了,这一章的内容,就是一个完整的调试和异常处理工具包。掌握好它们,你就能写出更健壮、更容易维护的Apex代码了。我们下节课继续深入。

    查看详情
  • 2

    Debug Log — Overview & Limits

    第 428 页

    同学们,咱们今天来聊一聊 Salesforce 里特别实用的一个功能——,调试日志,。你可以把它想象成 Salesforce 平台上的“黑匣子”,它会帮我们记录下系统背后发生的事情,当程序出错、或者你想搞清楚某个操作到底做了什么的时候,翻翻日志就明白了。 那日志都记录些啥呢?它主要关注这么几块: - ,数据库的变化,:比如你插入了一条客户记录,修改了某个字段,这些操作都会被记下来。 - ,HTTP 调用,:如果你的代码去调用外部的接口,比如请求天气数据、发送短信,这种对外访问的请求和响应也会被记录。 - ,Apex 代码里的错误,:这个是调试时最重要的部分,代码如果遇到异常、报错,会原原本本写进日志,方便你定位问题。 - ,系统资源的使用情况,:比如一段代码执行花了多少时间、消耗了多少 CPU 和内存,这样你就能分析性能瓶颈。 - ,自动化流程,:像触发器、流程、工作流规则这些自动化工具的运行过程,也会出现在日志里。 不过呢,日志可不是能随便无限制记录的,它有几条重要的限制,你一定得知道,不然可能遇到“日志丢了”或者“打不开了”的情况。 ,第一条,每条日志最大只能是 20 MB。, 如果一条日志太大了,系统会自动从日志的开头删除旧的行,只保留末尾 20 MB 的内容,所以你看到的可能不完整。这就像一本书太厚,只能撕掉前面几页给你看结尾。 ,第二条,日志保存的时间有硬性规定。, - 在“调试日志”页面里,我们手动查看的日志只保留 ,24 小时,,超过一天就自动清除了。所以查问题要趁热。 - 如果你在“监控”视图里看日志,数据会保留 ,7 天,,稍微宽松一点,但同样不是永久的。 ,第三条,和日志生成量有关的红线。, Salesforce 限制了每 15 分钟内日志的生成量,不能超过 ,1,000 MB,。如果超过了,系统就会把你所有的跟踪标志都禁用——也就是接下来不再给你生成任何日志了。而且,如果你累积的日志总量已经达到 1,000 MB,连添加或者修改跟踪标志都不允许了,等于被“关禁闭”,这时候必须等数据量降下来才能重新启用日志。所以千万别让代码在循环里疯狂打日志,那很容易触碰到红线。 这里补充一下,跟踪标志的关系,: 你在用户身上设置的跟踪标志,会决定日志记录的详细程度,比如只记错误,还是连每一步执行的细节都记。而类或触发器上也可以单独设置跟踪标志,它比用户级别的日志级别优先级更高——也就是说,如果类上设了更详细的记录级别,那用户级设的简单级别就没用了,会按类上的来。但是注意,,类/触发器上的跟踪标志只是覆盖日志级别,并不会单独触发日志生成,,谁来触发日志还得看用户上的跟踪标志有没有开启。 最后,有两点需要提醒:日志并不是万能的,有些东西它压根不记录。 - ,基于时间的工作流操作,(Time-dependent workflow actions),也就是那些将来某个时间点才执行的工作流动作,在到达执行时间之前的等待过程是看不到日志的。 - ,Visualforce 邮件模板的控制器,,当你用 Visualforce 模板给客户发邮件时,控制它显示内容的 Apex 代码部分,也不会出现在调试日志里。 好了,这节课把调试日志能做什么、有哪些限制都讲清楚了,下次当你排查问题的时候,就知道从哪里啃这块“黑匣子”了。有什么疑问随时提。

    查看详情
  • 3

    Debug Log — Sections & Structure

    第 429 页

    好,今天我们来看一个非常实用的主题——收件箱日志的格式。这就像是你在调试代码时的一本侦探手册,能帮你定位问题到底出在哪儿。 先看日志的开头,也就是Header部分。它非常规范,上面会清楚地显示你当前用的是哪个API版本,还有这次日志你选择了哪些类别以及它们的日志级别。这个很重要,因为有时候你发现日志里没有你想看的信息,很可能就是这里没选对日志级别。 接下来,你会看到日志里有一些大括号似的标记,比如“EXECUTION_STARTED”和“EXECUTION_FINISHED”。它们就像一个事务的起止点,告诉你整个交易从哪里开始,到哪里结束。知道了这个,你就能分清不同的操作是哪一个请求触发的。 在这个事务内部,又会有一些叫作代码单元的东西,标记是“CODE_UNIT_STARTED”和“CODE_UNIT_FINISHED”。这些代码单元就是一个个独立的工作块,可能是一个触发器(Trigger)、一个工作流规则(Workflow)、一个验证规则(Validation Rule),或者是一个@future方法、甚至是一个批处理。日志会帮你把每个工作块都用这样的小边界框出来,非常清晰。 然后我们再细看每一行日志,它们的格式是管道符分隔的,就像用竖线把不同字段隔开一样,很整齐。每一行都会带着一个精确到微秒的时间戳,格式是“HH:mm:ss.SS + 纳秒”,这样你就能看出事件发生的先后顺序。另外,每一行还有一个事件标识符,告诉你这是一个什么样的操作,比如是SOQL查询、DML操作,还是方法进入退出等等。 还有一个很有用的部分是TypRef,它会帮我们指出当前这段代码是哪个类或者哪个触发器内的。这样你读日志时,一眼就能知道出错的位置在哪儿文件里,非常方便。 日志末尾附近,你会看到一个累积资源使用情况的汇总。它可不是每个代码单元一结束就显示,而是等所有代码单元都跑完了,在事务结束前,统一给出一次累积分析,这样你就能看到这个事务总共消耗了多少CPU时间、查询了多少次数据库等等。 最后,关于堆(Heap,内存使用)的报告也很聪明。如果代码运行过程中没有错误,日志会显示一个“计算出的最大堆”值,告诉你整个过程中内存使用的峰值大概是多少。但一旦有错误发生,日志就会非常准确地报告出错误那一刻实际的堆使用情况,不给你任何估算,这样对查问题特别有帮助。 总结一下,理解收件箱日志这种结构化格式,就像掌握了读心术一样——代码在想什么、做了什么、哪里卡住了,全在这份报告里。希望大家以后在调试时,能像看老朋友写的一封信一样,轻松读懂它。好了,今天的内容就到这里,咱们下节课继续。

    查看详情
  • 4

    Debug Log — Categories, Levels & Event Types

    第 430 页

    同学们,我们今天来看一下Salesforce的收件箱日志,这个对咱们调试程序非常有帮助。 日志一共有10个类别,比如Apex代码、数据库操作、工作流等等。同时呢,日志级别从NONE到FINEST,总共有8个级别,级别是累积的,就是说你选了FINE,它也会包含DEBUG、INFO、WARN和ERROR这些更低级别的事件。 不过要注意,并不是所有级别在每一个类别里都适用。实际工作中,大部分活动是从INFO级别开始记录的,这是一个比较平衡的选择。 咱们再来看几个关键的事件组合。如果你要追踪Apex代码里的变量赋值,就需要把Apex Code这个类别设成FINEST,但这个要小心,因为它会记录敏感数据,比如密码、令牌之类的,一定不要在部署生产环境或者代码里有敏感信息时开FINEST。如果你只想记录方法的进出,用FINE级别就够了。 当你要看SOQL或SOSL查询执行情况的时候,把Database类别设成INFO就行,这样可以清晰地看到查询语句。如果你的代码对外部服务做标注请求,把Callout设成INFO,就能看到通过命名凭据发送的请求和响应。最后,关于工作流,把Workflow设成INFO,可以追踪标准工作流规则、字段更新和发送的邮件警报。 简单总结:开发调试多用INFO和FINE,FINEST一定要慎用,只在非生产环境且确认无敏感数据时开启。这样一来,日志给你的信息恰到好处,既不怕泄露,也不怕信息不足。

    查看详情
  • 5

    Debug Log — Trace Flags, Filters & Log Inspector

    第 431 页

    同学们,今天我们来聊一聊调试日志里几个非常实用但容易被忽略的细节。这些内容能帮你更高效地排查问题,也能让你看懂日志里那些“隐藏信息”。我们一个一个来,轻松一点,就像在聊天。 先说第一个:关于跟踪标志的覆盖。 你可能知道,可以在“用户”身上设置调试日志,也就是给某个用户开一个跟踪标志,这样该用户操作时系统就会记录日志。那如果我还想对一个特定的类或者触发器设置更细的日志级别呢?比如我给用户开了DEBUG级别的日志,但某个类我只想看ERROR级别的错误,不想被大量调试信息淹没。这时候就可以给这个类单独创建一个跟踪标志,设置成ERROR级别。 记住一个要点:,类或者触发器的跟踪标志会覆盖用户级别的跟踪标志,,但有个前提——它不会独立开启日志记录。也就是说,你得先确保那个用户的调试日志功能是开启的(用户有活动的跟踪标志),类/触发器的跟踪标志只是在这个前提下,把记录的级别替换成自己设置的。如果用户根本没开日志,类上的跟踪标志是不会凭空生成日志的。这一点很多同学容易搞混。 好,接下来看日志本身。打开一份调试日志,里面每一行其实都是一个“事件”。比如方法进入、代码单元结束、SOQL执行等等。这些,事件日志行有固定的格式,:先是事件名称,然后跟着行号,最后是一些针对该事件的详细描述。就像你看到的 `METHOD_ENTRY [10] myMethod()`,意思就是在第10行进入了某个方法,后面带上方法签名。这样设计是为了让工具和人都能快速解析。你在开发者控制台里看到每条日志前面的图标,就是根据事件名称来区分的。 这就引出了第三个点——,日志检查器,。在Developer Console的底部,当你打开一份日志,双击某一行时,旁边的面板会变成,上下文敏感的执行查看器,。它会自动给你展示这条日志对应的源代码、来自哪个触发器,以及当前事务的执行序列调用栈。你可以顺着这些信息快速跳转到代码里,非常直观。这个就是日志检查器,相当于一个智能导游,把日志和代码关联在一起。 然后注意一下视图的差别。Developer Console的日志有两种显示方式:,执行日志视图,和,原始日志视图,。你可以在底部的标签页切换

    查看详情
  • 6

    Debugging API Calls & Log Precedence

    第 432 页

    同学们,今天我们来聊聊在通过 API 调用 Salesforce 的时候,怎么精细地控制调试日志。 在 SOAP API 的请求里,我们可以传一个叫 ,DebuggingHeader, 的标头,它里面可以指定,日志类别,和,日志级别,。类别嘛,就是你想记录哪些方面的信息,比如数据库操作(DB)、工作流、验证规则等等,你可以按需勾选。级别则是从 ,NONE,(完全无日志)到 ,FINEST,(最详细)这个范围。 同时,对我们这些老手来说,以前的传统级别也照样能用,比如: - ,NONE,——不记录任何东西 - ,DEBUGONLY,——只记录调试信息 - ,DB,——记录和数据库相关的操作 - ,PROFILING,——用于性能分析 - ,CALLOUT,——记录外部接口调用的信息 - ,ALL,——什么都记下来 这些传统级别虽然简洁,但现在用类别加级别的方式会更灵活。 那么问题来了:如果我 API 里设了 DebuggingHeader,其他地方也有日志设置,到底听谁的?Salesforce 有一套非常明确的,优先级,规则,我给大家梳理一下: ,第一优先级:跟踪标志(TraceFlag), 这是最高的。如果你在开发者控制台、设置界面或通过工具 API,为某个用户或类开启了跟踪标志,它会直接覆盖所有其他的日志设置。也就是说,跟踪标志怎么说,最终日志就怎么生成。 ,第二优先级:测试执行时的默认级别, 如果没有设置任何跟踪标志,但你现在正在运行的是测试类(比如 `@isTest` 的单元测试),那么系统会应用一个默认的测试日志级别。所以就算没配标志,测试运行起来通常也能看到比较详尽的日志。 ,第三优先级:API 标头设置, 如果既没有跟踪标志,也不是在跑测试,那系统才会去看你通过 SOAP 头传过来的 ,DebuggingHeader,。你在这里设的类别和级别,会决定这次 API 调用生成多少日志。 ,第四优先级:入口点设置, 最后,还有一些入口点级别的设置,比如 Visualforce 页面上的 `debug` 请求参数:`?debug=1`。这种设置也可以生效,但它的优先级低于前面的几项。 ,最关键的细节,:如果以上任何一条都不适用——意味着没有 TraceFlag、不是测试、API 头部没设置、入口点也没要求——那么,系统就不会为本次操作生成或持久化任何日志,。你在日志列表里也就找不到它了。 大家记住这个优先级,以后排错的时候就能快速理解:为什么有日志、为什么没日志、到底谁说了算。今天的分享就到这里,有什么问题随时提出来。

    查看详情
  • 7

    Exceptions — Overview & What Happens

    第 433 页

    同学们,今天我们来看一下在Apex中如何用 throw 和 try-catch-finally 来优雅地处理错误。Slide上写的“使用throw生成Inbox”,这里可能有个小笔误——throw 是用来抛出异常的,也就是说,当我们发现一个错误情况,就“扔”出一个异常对象,而不是生成收件箱。大家知道这个意思就好。 好的,我们先说 throw 和 try-catch-finally 这套机制。你把可能出错的代码放在 try 块里,如果运行中真的出了问题,代码可以用 throw 抛出一个异常;紧接着,catch 块会抓住这个异常,你就可以在 catch 里记录日志或者给用户一个友好的提示。不管有没有异常,finally 块里的代码最后一定会执行,通常用来做清理工作,比如关闭文件流或者释放资源。 这套机制最大的优点就是异常会在调用堆栈中自动冒泡。什么意思呢?比如方法A调用方法B,方法B又调用方法C,如果C里抛出了一个异常,而C自己没有 try-catch 去捕获它,这个异常就会自动向上一层B传递;B如果也没捕获,就继续传给A。一直传到某层有 catch 把它接住,或者直接变成未处理异常。这样我们就不需要在每一个方法里都写一大堆错误检查代码,错误处理逻辑可以集中在一个地方,代码会干净很多。 当异常发生的时候,代码会立即暂停执行,当前事务的所有DML操作——也就是对数据库的修改——会自动回滚,这就像是“后悔药”,不会让出错的事务留下一堆半截子的脏数据。同时系统还会把这个异常记录下来,方便我们排查。 那如果异常一直没被捕获,变成了未处理异常呢?系统会做两件事:第一,给最后修改这段代码的开发人员发送一封通知邮件;第二,在用户界面上弹出一条错误提示。谁是这个开发人员呢?就是那个“Last ModifiedBy”,通常是最后一次保存类的同学。当然,这个邮件通知是可以配置的,你可以在Salesforce设置菜单里找到“Apex异常电子邮件”,或者通过工具API来添加自己要接收邮件的邮箱。 这里有个很贴心的设计:重复的异常会抑制后续邮件。也就是说,同一个错误如果在短时间内反复出现,系统只会发一次邮件,不会用海量邮件轰炸你的收件箱。不过要注意,每个应用服务器每小时最多只发10封异常邮件,所以不能全靠邮件来监控。 还有两个重要场景是不会发邮件的:一个是匿名Apex执行,也就是你在开发者控制台里随手运行的那几行代码;另一个是标了 @AuraEnabled 的Aura组件方法(Slide里写的是 @Aura Essentials,大家理解成Lightning组件的方法就行)。是因为这些场景下调用方形形色色,系统不知道把邮件发给谁才合适。 最后老师再多叮嘱一句:虽然配置了邮件通知很方便,但因为每小时有10封的限制,而且可能被重复异常抑制,我们不能只依赖邮件来发现问题。更科学的方式是借助事件监控(Event Monitoring)来跟踪异常,这样可以看到全量、实时的异常数据,帮助我们在系统出问题的时候快速定位,而不用担心漏掉关键信息。 好,关于异常处理的核心点就讲完了。简单回顾一下:用 throw 抛出异常,用 try-catch-finally 捕获处理,异常会自动冒泡,事务自动回滚并记录日志,未处理的会发邮件给最后修改者、同时提示用户,重复异常会抑制邮件,匿名执行和Aura方法不发邮件,每小时限10封,最好再配合事件监控一起用。大家课后可以自己写个小例子跑一跑,感受下异常冒泡的过程。有什么不清楚的,随时来问老师。

    查看详情
  • 8

    Exception Statements — throw, try, catch, finally

    第 434 页

    同学们,今天我们来聊聊Apex里怎么处理异常,也就是程序出错时该怎么优雅地应对。 其实整个思想很简单,就三步:发现问题就,抛出,一个异常对象,然后想办法,捕获,它,最后不管有没有错都要,收尾,。 先看抛出。 当你发现某个情况不应该继续执行,比如账户余额不足,你就可以用 `throw` 关键字,加上一个异常对象,把错误信号发出去。 比如 `throw new MyCustomException('余额不足');`,这就像你举起一个红牌,告诉系统:“这儿出问题了,后面的代码先别执行。” 那抛出之后,谁来接这个红牌呢?这就是 `try/catch/finally` 结构的作用。 `try` 里面放你“觉得可能出错”的代码。一旦里面有异常抛出,运行就会立刻跳到 `catch` 块。 `catch` 就像一个一个的捕手,每个捕手只抓一种特定的异常类型。 而且顺序很严格——,一个 catch 只能对应一种异常类型,类型还不能重复,。 异常对象来的时候,系统会从上往下找,先看第一个 `catch` 的类型是否匹配;一匹配上,就执行这个 `catch` 里的代码,后面的 `catch` 全部跳过,再也不会看了。 所以你必须把最特殊的异常放前面,把最一般的异常放最后。 一般我们会在最后放一个 `catch(Exception e)`,这个 `Exception` 是万能捕手,无论什么异常它都能接住。但是记住:它只能做最后一个,不然其他捕手就没机会上场了。 `finally` 块又是怎么回事呢? 它就像一个负责任的管家:,无论如何,最后都要执行,。 就算 `try` 里没有异常,或者异常被捕获了,甚至就算异常没被捕获又被抛出去了,`finally` 也一定会跑。 所以我们通常把清理工作放在这儿——关掉文件流、释放资源、删除临时数据,这些“必须得做”的事,交给 `finally` 最放心。 不过,Apex 里有些异常特别“霸道”,,根本无法捕获,。 哪怕你写了 `try/catch`,也没用,连 `finally` 都不会执行。 你要特别注意这几类: - 调控器限制导致的异常,比如 `LimitException`,也就是幻灯片上说的 `LimitResponse`; - 断言失败的某些系统级异常; - 还有许可证相关的异常,像缺少功能许可时抛出的错误。 遇到这些情况,程序会直接中断,没有挽回的余地。所以在编码时,要尽量避免触碰这些硬限制。 最后,在 API 版本 41.0 及之后,Apex 编译器变得更聪明了。 如果你写了一个无条件的 `throw` 语句,比如 `throw new MyException();`,然后下面还写了别的代码,编译器会直接报错,告诉你那段代码永远不会被执行到。 这其实是在帮我们提前发现逻辑错误,因为我们既然已经扔出了异常,后面的代码本来就没机会运行,删掉或者调整顺序才对。 总结一下: - 出问题就,扔,异常对象; - 用 `try/catch/finally` 来接住并处理,,具体异常放前,通用异常放最后,; - `finally` 永远跑,适合做清理; - 记住那几类抓不住的异常,别指望 `catch` 能救回来; - 新版本编译器还会管着你别写“死代码”。 这样一梳理,Apex 的异常处理是不是就清晰多了?我们下节课再见。

    查看详情
  • 9

    Exception Handling — Practical Examples

    第 435 页

    同学们,咱们今天聊一聊 Apex 里异常处理中一个特别关键的机制——,try-catch-finally,。这部分可能有些朋友觉得枯燥,但听我讲完,你会发现它就像生活中的保险绳,非常有用。 先看第一个小例子:假设你写了一段代码,想在数据库里插入一条客户信息。把插入语句放在 try 块里,后面还跟着一句“插入成功了,发个通知”的代码。如果插入时因为验证规则没通过而失败了,程序就会立刻跳进 catch 块去抓这个异常,注意了——try 块里那句“发通知”的代码,直接就被跳过了,,不会执行。但 catch 块后面的代码,比如打印一条“处理完成”的日志,还是会正常跑下去的。这就是 try-catch 最基本的保护:,出错时跳过剩余风险代码,但整个流程不崩盘,。 接下来更精彩的来了:,finally 模块,。我们再看一个写法——你在代码里用了一个 DataStreamWriter 来写文件,这可是个必须手动关闭的资源。想象一下,在 try 块里操作文件时,不小心访问了一个空字符串,于是抛出了空指针异常。程序会先进入 catch 记录错误,但无论异常是什么、甚至没异常,这个写文件的流如果不关掉,就会一直占用资源。这时 finally 块就派上用场了,你把关闭流的动作放进去,,它一定会执行,雷打不动,。这样一来,哪怕程序前面已经因为有异常而跳进了 catch,finally 还是会默默地把流关闭,保证资源不泄露。 这里有一个特别容易踩的坑大家要记住:,变量得声明在 try 块的外面,。比如 DataStreamWriter 这种流对象,你必须在 try 之前就定义好,不能写在 try 内部。因为如果定义在 try 里面,那它的作用域就被限制住了,catch 和 finally 都“看不见”它,自然也就无法在出错时去关闭它了。所以正确的做法是,先把变量声明在外层,再在 try 里给它赋值使用。 总结一下,,finally 块就像一位忠实的管家,不管你家是风平浪静(无异常),还是遭遇小偷(有异常),它都会在最后把门锁好(执行清理代码),。我们在写 Apex 时,只要涉及文件、网络连接、临时数据这类需要“用完就关”的资源,就用 try-catch-finally 这套组合拳,保你代码健壮又优雅。 好了,这个知识点我们就讲到这儿,下一个小节再见!

    查看详情
  • 10

    Built-In Exceptions & Common Methods

    第 436 页

    好,咱们来看看Apex里头内置的那些异常,还有查错时常用的几个方法。你平时写代码,肯定会遇到程序跑不下去、给你抛错误的情况,这些错误其实就是异常。Apex帮我们准备了好几种内置的异常类型,每一种都对应了常见的问题场景,方便我们快速定位。 比如,`DmlException`,它是当你做数据库操作失败了才抛出来的,比如插入、更新记录时,因为验证规则没通过,或者触发器里限制住了,就会触发这个异常。 `ListException`,这个一看名字就知道跟列表有关。典型的情况就是下标越界 —— 你试图去取列表里根本不存在的位置,比如一共才三个元素,你却要拿第四个,它就会抛出来。 `NullPointerException`,这个太经典了,空引用。你对着一个null的东西调用方法或者访问属性,肯定会报这个错。 `QueryException`,跟SOQL查询有关。比较常见的情形是,你用 `SELECT` 把查询结果直接赋值给一个单条记录的变量,结果回来的却是零行,系统就不知道该给你什么,于是抛出 `QueryException`。有时候你想当然地以为至少有一行数据,可实际上它就是没有,要特别留意。 还有 `SObjectException`,这个通常发生在你试图访问SObject上根本没查询过的字段。比如你只查了Name,却在代码里去取Phone,系统就会告诉你:这个字段没被查出来,你不能直接访问。 光知道异常类型还不够,我们还得能读懂错误信息。Apex的异常对象提供了一些特别好用的方法,咱们挨个说: - `getTypeName()`,返回的是异常的类型名称,也就是刚才说的那几类,比如“System.NullPointerException”,一眼就知道什么毛病。 - `getMessage()`,返回具体的错误消息文本。这个很直观,告诉你到底哪里出了问题。 - `getLineNumber()`,返回发生异常的那行代码行号。查 bug 的时候,这个太有用了,直接把你带到事发现场。 - `getCause()`,返回内部异常对象,如果这个异常是被另一个异常包装起来的,就能通过它追溯到根因;要是没有,它就是 null。 - `getStackTraceString()`,返回完整的堆栈跟踪字符串。从最上层的调用一直显示到出错的地方,每一步调用关系都列出来了,非常适合详细了解执行路径。 你可能会在捕获异常时这么写:先用一个比较通用的 `Exception` 类型来 catch,然后根据实际抛出的更具体的异常,比如 `QueryException`,来做不同的处理。因为所有的异常都继承自 `Exception`,所以通用的 catch 块可以兜底,但如果你要针对性处理,就可以先捕获更具体的异常。 举个例子吧,假设你写了一段查询联系人的代码,你期望至少查到一个联系人,于是直接写成: ```apex Contact c = [SELECT LastName FROM Contact WHERE ...]; ``` 可惜,实际可能因为条件不满足,查出来 0 行,这时就会抛出 `QueryException`。你为了不让程序中断,可以这样写: ```apex try { Contact c = [SELECT LastName FROM Contact WHERE ...]; System.debug('联系人姓:' + c.LastName); } catch (QueryException qe) { System.debug('查询出错,未找到记录:' + qe.getMessage()); } catch (Exception e) { System.debug('发生了其他错误:' + e.getTypeName()); System.debug('堆栈:' + e.getStackTraceString()); } ``` 在这个例子里,先捕获更具体的 `QueryException`,单独处理查询行数的问题;如果抛出的是其他异常,比如空指针,就会被后面的通用 `Exception` 捕获,并且我们还能用 `getTypeName()` 和 `getStackTraceString()` 获得详细信息去排查。 这样一层层捕获,能让你的代码更稳健,也更容易调试。当生产环境出了问题,靠这些方法马上就能知道是什么异常、在哪一行、为什么会发生。 好了,关于内置异常和常用方法就先说到这儿,你可以在练习里多试试不同的场景,加深印象。

    查看详情
  • 11

    DmlException — Per-Record Methods

    第 437 页

    好,同学们,我们来看一下这张片子,它讲的是 ,DmlResponse, 。这个 DmlResponse 啊,专门为批量操作中部分失败的情况,提供每条记录的错误信息。 当我们要处理一大批记录的时候,比如说批量 TLR 操作,很可能会碰到“一部分成功、一部分失败”。那怎么知道哪条记录失败了、为什么失败呢? DmlResponse 就给了我们几个非常有用的小方法。第一个,,getNumDml(),,它会返回一个数字——告诉你到底有多少条记录失败了。接下来,你可以用索引去查具体是哪些记录出问题。 比如 ,getDmlFieldName(index),,它会返回导致错误的是哪个字段;,getDmlMessage(index), 返回这里出错的具体消息;还有 ,getDmlId(index),,它会返回那条失败记录的 ID。这样你就能很快定位到问题。 这片子里给的例子,是插入 3 条记录,结果其中 2 条插不进去。程序就用 DmlResponse 正确报告了失败数量是 2,还能为每个失败记录给出对应的字段名字和错误消息。 所以你看,在写 Apex 的数据库方法处理部分成功时,用上 DmlResponse 真的太关键了,它能帮你清晰地捕捉和展示错误细节,让调试和维护变得轻松不少。 好,这一页我们就讲到这里。

    查看详情
  • 12

    Catching Different Exception Types

    第 438 页

    同学们,我们来讲一下异常处理里一个很重要的规则:当你写了多个 catch 块的时候,它们是怎么匹配的,以及为什么顺序一定要安排好。 先说清楚一点:当 try 代码块里抛出一个异常后,系统会从上往下、按你写的顺序,一个一个检查 catch 块。只要找到第一个能匹配这个异常类型的 catch,它就立刻跑进去执行里面的代码,然后其他所有的 catch 块会被直接跳过,根本不会去看。 这就像在几条巷子里找能对得上钥匙的锁,一旦打开了某扇门,就不会再看后面的门了。 所以关键词就是:,第一个匹配的获胜,后面的全部绕开。, 那这个规则会带来什么问题?我们来看一个典型例子。假设我们写了三个 catch 块,顺序是这样的:先 catch 一个 DmlException,然后 catch 一个 SObjectException,最后 catch 一个通用的 Exception。这个顺序就是对的。为什么呢? 我们拿两种不同的异常来推演一下。 第一种场景:如果代码里抛出了一个 SObjectException。系统开始检查第一个 catch——咦,它是 DmlException,不匹配,跳过。接着看第二个 catch,刚好是 SObjectException,匹配上了!那就立刻进入第二个 catch 块处理,然后第三个通用的 Exception 就完全不看了。也就是说,特定的 SObjectException 被专门处理掉了。 第二种场景:如果代码抛出的不是上面那两个,比如一个 NullPointerException。系统先看第一个 DmlException,不匹配;再看第二个 SObjectException,也不匹配;最后看到第三个 catch 是 Exception,它是所有异常的父类,所以什么都能接住,于是就由它来处理。这样,NullPointerException 就被通用处理给兜底了。 从这个过程你能看出来,我们把最特指的异常写在前面,把最泛化的 Exception 写在最后,就能做到:不同类型的异常,能进到专门为它准备的 catch 块里处理;而意料之外的异常呢,最后还有一个“大口袋”兜着,不会让程序崩溃。 反过来,如果你把 catch(Exception) 写在第一个,那后面的 DmlException、SObjectException 这些 catch 块永远都不会执行,因为任何异常都会被第一个通用口子给截胡了,Apex 编译器甚至会直接报错,提醒你代码不可达。所以不管从编译角度还是运行逻辑,都必须让特定异常在前,通用异常在后。 这个模式就能确保每一个异常类型都被恰当地处理:该专门处理的专门处理,该统一兜底的统一兜底。请大家在写代码的时候一定记住这个顺序原则,避免出现莫名其妙被跳过或不处理的情况。 好了,这一节就讲到这里,下次遇到 try-catch,你就知道该怎么排列 catch 块的顺序了。

    查看详情
  • 13

    Custom Exceptions & Inner Exceptions

    第 439 页

    同学们,咱们今天来看一下Salesforce里自定义异常这个知识点,其实很简单,我们自定义异常就是扩展了内置的Exception类。要注意,按照Apex的规范,自定义异常类的名字必须以Exception结尾,比如MyCustomException,不这么写编译器会报错。 这个自定义的异常类会继承Exception所有常用的方法,同时我们还可以给它加上自己的成员变量、额外的方法,甚至让它去实现接口,非常灵活。另外,它自动继承了Exception的四个构造器,所以我们不用重写构造器,直接拿过来用就行。 这里有一个很实用的模式叫“内部异常”或者说“重新抛出”模式。什么意思呢?就是我们在catch到一个底层异常的时候,不是直接抛出原来的异常,而是新建一个我们自己的业务异常,在构造这个新异常的时候,把原来的异常对象作为“原因”传进去。这样做的好处是保留了完整的错误链,堆栈跟踪信息里会同时显示原来的异常和我们新抛出的异常,方便我们一层层追查问题根源。 我举个具体例子,咱们有一个叫做DeliverdiseUtility的工具类,里面有个insertDeliverdiseance方法,它尝试去插入记录,如果插入时发生DmlException(也就是数据库操作异常),就会被catch住。此时我们会创建一个新的DeliverdiseanceException,然后把捕获到的DmlException作为原因传进去,再把这个新的异常抛出去。 在更上层的mainProcess方法里,我们就可以去捕获这个DeliverdiseResponseException(这里可能是笔误,应该是DeliverdiseanceException),拿到异常后,除了可以读取它自己的消息,还能通过getCause方法拿到里面那个原始的DmlException,甚至可以从堆栈里拿到出错的准确行号。这样从外到内,从业务异常到底层异常,信息一目了然。 所以总结一下,自定义异常就是继承Exception,命名要规范,可以往里面加东西,重新抛出时利用cause保留错误链。这个小技巧在实际项目里特别常用,能帮我们快速定位问题,让错误处理更清晰。好了,这一页就讲这么多。

    查看详情