编写更优质的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代码是一个持续学习和优化的过程,希望以上内容能给开发者带来一些帮助。
超级会员免费看
1722

被折叠的 条评论
为什么被折叠?



