12、编写更优质的Apex代码

编写更优质的Apex代码

在Apex开发中,编写高质量的代码对于提升开发效率和确保系统稳定性至关重要。下面将从基础和高级两个层面介绍编写更好Apex代码的方法。

理解语言基础

要编写更好的Apex代码,打好基础是关键。这部分将聚焦于Apex代码文件的正确命名规范以及语言结构(如静态关键字、循环、常量等)。

代码文件的正确命名规范

在Apex中,规范代码文件的命名非常重要。因为所有的Apex类都存储在同一层级,开发者无法使用多级嵌套的包或命名空间。在中型项目中,很容易出现超过50个Apex类的情况。如果多个开发者参与项目且没有统一的命名规范,不查看代码就很难知道每个Apex类文件的用途。以下是一些命名建议:
- 遵循统一的命名标准 :Force.com团队强烈推荐采用Java标准,使用驼峰命名法(Camel Case)。具体规则如下:
- 类名 :以大写字母开头,例如 AccountTriggerHandler
- 方法名 :以小写动词开头,例如 loadAllContacts()
- 变量名 :采用小驼峰命名法,例如 Integer numberOfContacts
- 常量名 :全部大写,单词之间用下划线分隔,例如 MAX_NO_CHILDS = 10
- 使用前缀或后缀描述Apex类的性质 :这里的性质指的是类是否为批处理、定时任务、异步任务、触发器、页面控制器或扩展类。添加前缀或后缀可以让开发者在不查看代码的情况下,更容易定位和了解类的用途。以下是一些根据用途添加前缀的Apex类名示例:
- trgr_ContactDuplicateCheck :用于联系人触发器的类。
- trgr_OpportunityAmountValidator :用于机会触发器的类。
- page_OppLoadController :用于机会对象的自定义控制器类。
- page_ContactMergeExtension :用于联系人对象的自定义Visualforce扩展类。
- ws_ContactService :作为SOAP Web服务公开的Apex类。
- btch_BulkRecordCleaner :用于清理记录的批处理作业Apex类。
- schd_NightlyAccountSync :每晚运行的定时任务。
- 避免过多的类文件 :有Java开发背景的开发者习惯将功能分散到多个类文件中,但在Apex中这种做法并不理想。建议将功能集中在最少的Apex类中,原因如下:
- 依赖管理困难 :过多的文件会使依赖管理变得复杂,Apex中没有直接的方法来查看一个类、方法或变量在其他类中的引用位置。
- 影响Apex流程性能 :过多的类会减慢Apex流程,因为每次调用时都会触发验证例程,以确保依赖关系正确。

语言结构

大多数编程语言的基本语言结构表现相似,但Apex在某些方面有所不同,例如静态变量。下面将介绍Apex中静态关键字的特点。

  • 静态关键字的不同之处 :Apex中的静态与其他语言(如Java)有很大区别。在Java中,静态变量一旦设置,会在应用程序的整个生命周期内持续存在,直到应用程序、服务器或容器关闭,或者应用程序的新版本热部署。而在Apex中,静态变量的生命周期随着请求或流程的开始和结束而结束,因为Apex类不会永久缓存在内存中,当通过页面或触发器收到请求时,它们会重新加载到内存中。因此,不要期望像单例模式(Singleton Pattern)这样的模式在Apex中能正常工作。不过,静态关键字在以下情况下仍然有用:
    • 在流程中跨类共享信息 :虽然这不是一个好的设计,但在某些情况下可能会有帮助,特别是在对某些共享变量进行加锁时。
    • 控制触发器 :避免同一触发器再次触发。静态变量在同一流程中的多次触发器执行中保持持久化,即如果对象X的触发器对对象Y执行DML操作,静态变量的值将在从X到Y的整个流程中保持不变。这可以用于链式触发器,防止其无限触发。
    • 不成为视图状态的一部分 :除了常量外,一些变量也可以声明为静态,因为静态变量不会成为视图状态的一部分。
  • 简化循环 :Apex支持所有形式的循环,如do - while、while、基于索引的for循环和for - each循环。对于整数数组,开发者经常使用传统的基于索引的for循环,例如:
Integer[] ants = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(Integer idx = 0; idx < ants.size(); idx++) {
    System.debug('Iterating :' + ants[idx]); 
}

可以将其简化为for - each循环:

for(Integer ant : ants) {
    System.debug('Iterating :' + ant); 
}

建议使用for - each循环,除非明确需要索引进行某些计算,这样可以避免一些运行时错误,如索引越界。
- 使用枚举优化常量 :开发者通常将常量声明为静态最终变量,例如:

public static final String WEATHER_SPRING = 'SPRING';
public static final String WEATHER_SUMMER = 'SUMMER';
public static final String WEATHER_WINTER = 'WINTER';

这种方法可行,但这些常量本质上是相关的,都代表天气。因此,它们更适合用枚举来声明:

public enum Weather {WINTER, SPRING, SUMMER}

将相关常量声明为枚举可以提供更多的比较和类型安全。
- 减少脚本语句 :Apex代码受到严格的限制,目前每个流程的脚本语句限制为200,000条。这意味着可以跨多个类运行Apex代码,但执行的脚本/代码行总数不能超过200,000条。在编写复杂逻辑时,必须牢记这一重要限制。例如,创建一个Map通常需要四行脚本:

Map <Integer, String >aMap = new Map <Integer, String>();
aMap.put(1 , 'a');
aMap.put(2 , 'b');
aMap.put(3 , 'c');

可以将其减少为一行脚本:

Map <Integer, String>aMap = new Map <Integer, String> {1 => 'a', 2 => 'b', 3 => 'c'};

同样,可以用类似的方式创建Set和List:

Set <String> aSet = new Set <String>{'a', 'b', 'c'};
List<String> aList = new List<String>{'a', 'b', 'c'};

还可以直接从SOQL查询创建Map:

Map<ID, Contact>contacts = new Map<ID, Contact>([SELECT Id, LastName FROM Contact]);

需要注意循环中执行的代码的脚本行数,它们会随着迭代次数而增加。可以在开发者控制台中执行代码并查看日志,以测试脚本语句的使用情况。

高级Apex技巧

这部分将讨论Apex中的高级主题,如SOQL查询、数据安全、XML/JSON处理和Web服务等。

数据安全访问

Apex在系统上下文中运行,会绕过安全机制,即当前用户的权限、字段级安全和共享规则。Apex允许用户在声明类时启用或禁用共享规则。以下是一个示例:

public with sharing class AccountExtension {
    public Account[] init() {
        // 除了匹配条件外,仅返回当前用户有权限查看的账户
        return [Select Id, Name from Account Where Name like '%corp'];
    }
}

如果使用 without sharing 关键字编写代码,将返回所有账户,这可能会导致严重的安全漏洞,对企业客户造成损害。如果类声明时未使用 with sharing without sharing 关键字,它将继承调用类或父类的共享设置。

强制实施CRUD和FLS

Apex仅通过 with sharing without sharing 关键字来支持数据安全,但没有直接的方法来强制实施sObject的创建、读取、更新和删除(CRUD)以及字段级安全(FLS)。一些Visualforce标准标签(如 <apex:outputField /> <apex:inputField /> )会自动强制执行FLS。一般来说,如果登录用户的配置文件没有所需的权限,最好优雅地处理权限缺失错误。为了强制实施CRUD/FLS,开发者可以使用Force.com ESAPI库,该库提供了方便的函数来检查sObject的CRUD/FLS权限。

编写更好的SOQL查询

以下是一些编写高效SOQL查询并遵守限制的技巧:
- 仅查询所需字段 :开发者通常使用Eclipse Schema Explorer构建包含sObject所有字段的查询,例如:

Account[] accounts = [Select a.mycustomfield__c, a.Website, a.Type, a.TickerSymbol, a.SystemModstamp, a.Site, a.Sic, a.ShippingStreet, a.ShippingState, a.ShippingPostalCode, a.ShippingCountry, a.ShippingCity, a.Rating, a.Phone, a.ParentId, a.Ownership, a.OwnerId, a.NumberOfEmployees, a.Name, a.MasterRecordId, a.LastModifiedDate, a.LastModifiedById, a.LastActivityDate, a.JigsawCompanyId, a.Jigsaw, a.IsDeleted, a.IsCustomerPortal, a.Industry, a.Id, a.Fax, a.Description, a.CreatedDate, a.CreatedById, a.BillingStreet, a.BillingState, a.BillingPostalCode, a.BillingCountry, a.BillingCity, a.AnnualRevenue, a.AccountNumber From Account a];

这种查询会加载很多在代码中几乎不会引用的字段,会带来以下影响:
- 堆内存消耗高 :特别是当有大量记录或不必要的文本区域字段时。
- Visualforce页面变慢 :如果在页面上使用了上述账户集合。
- 创建无效依赖 :对自定义字段(如 mycustomfield__c )创建无效依赖。如果该字段被错误或意外创建,在重命名或删除时会出现问题。
因此,SOQL查询应仅包含实现目的所需的最少字段。如果需求发生变化,最好修改SOQL以添加更多字段。
- 使用SOQL for循环 :如果预计SOQL查询会返回大量记录,建议将其重构为使用SOQL for循环。例如,以下是一个标准的SOQL查询来加载账户:

Account[] accounts = [Select Id, AccountNumber, Remarks__c From Account];
for (Account acc: accounts) {
    // 处理逻辑
}

假设这个SOQL查询返回20,000行,并且 Remarks__c 自定义字段是一个255字符的文本区域。这段代码很容易超过允许的最大堆内存限制(6 MB)。可以将其重构为使用SOQL for循环:

for (Account acc: [Select Id, AccountNumber, Remarks__c From Account]) {
    // 处理逻辑
}

SOQL for循环以200条记录为一批加载数据。
- 执行选择性和基于索引的查询 :SOQL查询通常应具有选择性,并在索引字段上设置过滤器。如果Account sObject的记录数超过50,000条,以下SOQL查询将失败:

Select Id, Name from Account

这种一次性处理所有账户的情况很少见,如果需要处理大量数据,使用批处理Apex是更好的选择。官方Apex文档指出,在索引字段上设置过滤器的SOQL查询性能更好。以下标准字段会自动索引:
- 主键 :ID、Name和Owner。
- 外键 :查找或主 - 明细关系。
- 审计字段 :Last Modified Date。
- 标记为外部ID或唯一的字段
如果经常在自定义字段上进行过滤,可以向Salesforce支持团队请求为这些字段创建自定义索引,以提高性能。
- 合并多个SOQL查询以避免限制 :以下代码片段需要两次SOQL调用:

Account[] accs = [Select Id, Name from Account Where Name like 'a%'];
Contact[] contacts = [Select Id, Name from Contact Where AccountId in :accs];

可以将其合并为一次SOQL调用:

Account[] accs = [Select Id, Name, (Select Id, Name from Contacts) from Account Where Name like 'a%'];
for (Account acc: accs) {
    Contact[] contacts = acc.Contacts;
    // 处理联系人
}

合并多个相关对象的SOQL查询可以降低违反限制的风险,特别是在代码流程中执行大量SOQL查询时。
- 处理返回单条记录的SOQL查询 :以下SOQL查询加载单条记录:

Account acc = [Select Id from Account where Name like '%Corp'];

这个查询在某些条件下容易出现以下异常:
- System.QueryException: List has no rows for assignment to sObject :当没有匹配的记录时。
- System.QueryException: List has more than 1 row for assignment to sObject :当有多个匹配的记录时。
可以通过以下两种方式优雅地处理这些问题:
- 查询为列表

Account acc = null;
Account[] accs = [Select Id from Account where Name like '%Corp'];
if (accs != null && !accs.isEmpty()) {
    acc = accs[0];
} else {
    System.debug ('No account found');   
}
- **使用try - catch块**:
Account acc = null;
try {
    acc = [Select Id from Account where Name like '%Corp' limit 1];
} catch (System.QueryException qe) {
    // 如果记录应该存在,在此抛出错误
    // 如果记录是可选的,在catch块中不做任何处理
}
if (acc != null) {
    // 依赖于acc的其余逻辑
} else {
    System.debug ('No account found');   
}

使用返回单条记录的SOQL查询时,应小心并在所有可能的条件下进行测试,以避免运行时问题。
- 充分利用动态SOQL :动态SOQL允许开发者根据流程状态和用户提供的条件在运行时构建SOQL字符串。大多数Apex开发者对动态SOQL中不支持变量绑定存在误解,实际上是支持的。以下代码片段证明了这一点:

String name = '%Acme%';
String qryStr = 'SELECT Id FROM Account WHERE Name like :name';
Account[] accs = Database.query(qryStr);

变量绑定的工作方式与普通SOQL相同,会在各种作用域(如局部变量、参数、实例级属性等)中扫描绑定变量。使用动态SOQL时,开发者应注意以下几点:
- 运行时错误 :查询语法错误在运行时才会出现(没有编译时检查),如果基于大量条件构建复杂的SOQL查询,务必在所有条件下测试生成的查询,以避免运行时代码崩溃。
- 优先使用普通SOQL :先尝试使用普通SOQL语句解决问题,只有在必要时才使用动态SOQL。
- 安全问题 :不正确使用动态SOQL可能会使应用程序受到SOQL注入攻击。

控制DML操作

Apex运行时的事务控制在流程或请求级别工作。只有当整个流程成功时,更改才会提交到数据库,否则所有操作都会回滚。有时在流程中需要对一组DML语句进行精细控制。可以参考Force.com博客上的文章了解保存点的使用示例,以及Apex开发者指南中关于事务控制的文章,以了解更多关于事务控制的信息。

综上所述,通过掌握这些基础和高级技巧,可以编写更高效、更安全的Apex代码。在实际开发中,应根据具体需求选择合适的方法,并不断优化代码,以提高系统的性能和稳定性。

编写更优质的Apex代码

处理XML和JSON响应

在Apex中,经常需要处理XML和JSON格式的数据,比如与外部系统交互时。以下是处理这两种数据格式的相关内容。

  • JSON处理 :Apex提供了 JSON.deserialize JSON.serialize 方法来处理JSON数据。例如,将一个对象序列化为JSON字符串,再将JSON字符串反序列化为对象。
// 定义一个简单的类
public class Person {
    public String name;
    public Integer age;
}

// 创建对象
Person p = new Person();
p.name = 'John';
p.age = 30;

// 序列化为JSON字符串
String jsonStr = JSON.serialize(p);

// 反序列化为对象
Person newP = (Person)JSON.deserialize(jsonStr, Person.class);
  • XML处理 :可以使用 DOM (文档对象模型)或 SAX (简单API for XML)来处理XML数据。 DOM 适合处理较小的XML文档,它会将整个XML文档加载到内存中; SAX 则适合处理大型XML文档,它是基于事件驱动的,逐行解析XML。以下是一个使用 DOM 解析XML的示例:
String xmlStr = '<root><name>John</name><age>30</age></root>';
Dom.Document doc = new Dom.Document();
doc.load(xmlStr);
Dom.XmlNode root = doc.getRootElement();
String name = root.getChildElement('name', null).getText();
Integer age = Integer.valueOf(root.getChildElement('age', null).getText());
打包Apex代码的考虑因素

在打包Apex代码时,需要考虑以下几个方面:

  • 依赖管理 :确保打包的代码所依赖的其他类、对象和资源都包含在包中,避免出现依赖缺失的问题。
  • 版本控制 :明确代码的版本,方便后续的维护和升级。可以使用Salesforce的版本管理工具来管理代码版本。
  • 安全性 :检查打包的代码是否存在安全漏洞,如SQL注入、数据泄露等。确保代码遵循安全最佳实践。
  • 兼容性 :考虑代码在不同Salesforce版本和环境中的兼容性,避免出现兼容性问题。
充分利用API版本

API版本在Apex开发中非常重要,不同的API版本可能会有不同的功能和特性。在选择API版本时,需要考虑以下几点:

  • 功能需求 :根据项目的功能需求选择合适的API版本。如果需要使用某些新功能,可能需要选择较新的API版本。
  • 兼容性 :确保选择的API版本与现有的代码和系统兼容。如果使用了较新的API版本,可能需要对现有代码进行一些调整。
  • 稳定性 :较新的API版本可能会存在一些不稳定的问题,在选择时需要权衡稳定性和新功能的需求。
Apex测试技巧

编写高质量的测试代码对于保证Apex代码的正确性和稳定性至关重要。以下是一些Apex测试的技巧:

  • 测试覆盖率 :确保测试代码能够覆盖尽可能多的代码路径,提高测试覆盖率。可以使用Salesforce的测试覆盖率工具来检查代码的测试覆盖率。
  • 数据隔离 :在测试中使用隔离的数据,避免测试数据对生产数据造成影响。可以使用测试数据工厂来创建测试数据。
  • 异常处理测试 :测试代码在异常情况下的表现,确保代码能够正确处理各种异常。例如,测试SOQL查询在没有匹配记录时的异常处理。
@isTest
private class AccountTest {
    static testMethod void testSingleRecordQuery() {
        try {
            Account acc = [Select Id from Account where Name like 'NonExistentName'];
            System.assert(false, 'Should throw an exception');
        } catch (System.QueryException qe) {
            System.assert(true, 'Exception caught as expected');
        }
    }
}
RESTful Web服务技巧

在Apex中开发RESTful Web服务时,需要注意以下几点:

  • 请求处理 :正确处理不同类型的HTTP请求(GET、POST、PUT、DELETE等),根据请求类型执行相应的操作。
  • 响应格式 :返回合适的响应格式,如JSON或XML。确保响应数据的格式正确,易于客户端解析。
  • 安全性 :对RESTful Web服务进行安全防护,如使用身份验证和授权机制,防止未经授权的访问。
理解Apex限制

Apex有许多限制,如CPU时间限制、SOQL查询次数限制、DML语句次数限制等。了解这些限制并在开发中遵守它们是非常重要的。以下是一些常见的限制及应对方法:

限制类型 具体限制 应对方法
CPU时间限制 同步事务10,000毫秒,异步事务60,000毫秒 优化代码逻辑,减少不必要的计算;使用批处理Apex处理大量数据
SOQL查询次数限制 同步事务100次,异步事务200次 合并多个SOQL查询;使用SOQL for循环处理大量数据
DML语句次数限制 同步事务150次,异步事务无限制 批量执行DML操作,减少DML语句的调用次数

总结

编写优质的Apex代码需要掌握基础的语言知识和高级的开发技巧。从代码文件的命名规范到高级的SOQL查询优化,从数据安全控制到RESTful Web服务开发,每个环节都需要我们认真对待。同时,要充分理解Apex的各种限制,在开发过程中遵守这些限制,以确保代码的性能和稳定性。通过不断学习和实践,我们可以编写出更加高效、安全、可维护的Apex代码。

下面是一个处理SOQL查询和DML操作的简单流程图:

graph TD;
    A[开始] --> B[编写SOQL查询];
    B --> C{查询结果是否符合预期};
    C -- 是 --> D[执行DML操作];
    C -- 否 --> E[优化SOQL查询];
    E --> B;
    D --> F[检查DML操作结果];
    F --> G{操作是否成功};
    G -- 是 --> H[结束];
    G -- 否 --> I[处理异常];
    I --> B;

总之,编写优质Apex代码是一个持续学习和优化的过程,希望以上内容能给开发者带来一些帮助。

【电能质量扰动】基于ML和DWT的电能质量扰动分类方法研究(Matlab实现)内容概要:本文研究了一种基于机器学习(ML)和离散小波变换(DWT)的电能质量扰动分类方法,并提供了Matlab实现方案。首先利用DWT对电能质量信号进行多尺度分解,提取信号的时频域特征,有效捕捉电压暂降、暂升、中断、谐波、闪变等常见扰动的关键信息;随后结合机器学习分类器(如SVM、BP神经网络等)对提取的特征进行训练与分类,实现对不同类型扰动的自动识别与准确区分。该方法充分发挥DWT在信号去噪与特征提取方面的优势,结合ML强大的模式识别能力,提升了分类精度与鲁棒性,具有较强的实用价值。; 适合人群:电气工程、自动化、电力系统及其自动化等相关专业的研究生、科研人员及从事电能质量监测与分析的工程技术人员;具备一定的信号处理基础和Matlab编程能力者佳。; 使用场景及目标:①应用于智能电网中的电能质量在线监测系统,实现扰动类型的自动识别;②作为高校或科研机构在信号处理、模式识别、电力系统分析等课程的教学案例或科研实验平台;③目标是提高电能质量扰动分类的准确性与效率,为后续的电能治理与设备保护提供决策依据。; 阅读建议:建议读者结合Matlab代码深入理解DWT的实现过程与特征提取步骤,重点关注小波基选择、分解层数设定及特征向量构造对分类性能的影响,并尝试对比不同机器学习模型的分类效果,以全面掌握该方法的核心技术要点。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值