0% found this document useful (0 votes)
50 views23 pages

B W Base Coding Standards

Uploaded by

Francys Garcia
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
50 views23 pages

B W Base Coding Standards

Uploaded by

Francys Garcia
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

c Bluewolf, an IBM Company 2016-2017

c Copyright IBM Corporation 2018 — All rights reserved

Apex Coding Standards v0.4


Date: May 17, 2018
Contents

Contents ii

1 Introduction 1
1.1 Purpose of this Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

2 Formatting 2
2.1 Method Naming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.2 Whitespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.3 Brackets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.4 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.5 Variable Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.6 Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.7 Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.8 Static Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.9 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.10 Unused Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.11 SOQL Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3 Implementation Standards 9
3.1 Implementation Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Dataflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.3 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.4 Prohibitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.5 Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.6 API Design Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4 Unit Testing 18
4.1 Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.2 Code Coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.3 Goals of Unit Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.4 Test Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.5 Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

Bibliography 21

ii
Chapter 1

Introduction [Intro.Scope]

(1)
The language in this document follows the ISO Directive standards as such:

• “shall” indicates a requirement


• “should” indicates a recommendation
• “may” is used to indicate that something is permitted
• “can” is used to indicate that something is possible, for example, that an orga-
nization or individual is able to do something
In the ISO/IEC Directives, Part 2, Seventh edition, 2016, 3.3.3, a requirement is
defined as an “expression in the content of a document conveying objectively verifiable
criteria to be fulfilled and from which no deviation is permitted if compliance with
the document is to be claimed.”
In the ISO/IEC Directives, Part 2, Seventh edition, 2016, 3.3.4, a recommendation
is defined as an “expression in the content of a document conveying a suggested pos-
sible choice or course of action deemed to be particularly suitable without necessarily
mentioning or excluding others.”

1.1 Purpose of this Document [Intro.Purpose]


(1.1) This is a teaching document for development in the Apex Language. The document is
intended to bring new developers onboard, teach coding standards, and document best practices.
This document should describe the best practices, and explain the reasoning behind why these
practices are considered the best design decisions.

1
Chapter 2

Formatting [Format]

(2)
This section is designed to provide a consistent style for all Bluewolf developers. If a developer
cannot find an answer to their styling question they should fall back on the Google Java Style
guidelines. However this document is considered authoritative where the two differ.

2.1 Method Naming [Format.Names]


(2.1)
Clear method naming significantly reduces the time it takes a developer to rationalize about
the behavior of code. It is extremely important that code is self documenting.
1 Method Names shall use Camel Case and shall start with a lowercase letter

Example:
void assignTeamMembersToAccounts(List<Account> accounts)
2 Method names shall describe the functionality of the method

Example:
void assignTeamMembersToAccounts(List<Account> accounts)

The method should only assign an account team members to accounts and should have no
other side effects
3 Methods should do their best to indicate if side effects occur

Example:
void upsertAccountTeamMembers()

The method very clearly indicates that it performs DML


4 Test methods shall indicate the behavior of the method being tested

Example:
static testMethod void whenNoAccountTeamMembers_nothingHappens()

2
2.2 Whitespace [Format.Whitespace]
(2.2)
1 All code shall use 4 spaces for indentation
2 There shall be no whitespace beyond the last non-whitespace character on the line
3 If the line has no-content; then it shall have no whitespace other than the carriage return
4 If the file has mixed white space the developer shall convert the file prior to making any edits
5 The file shall be committed to the repository prior to any further work being done
6 A developer should try to keep all code within 120 columns
This rule arises for a few reasons:
• When reviewing code it’s easier to read if the code does not wrap
• Having long lines makes the code more difficult to manipulate

2.3 Brackets [Format.Brackets]


(2.3)
1 Brackets should be kept consistent throughout a file [ Note: This shall mean indentation, and
style — end note ]
2 If and else blocks shall be bracketed
3 The default bracket style should be “Egyptian style” brackets

• No line break before the opening brace.


• Line break after the opening brace.
• Line break before the closing brace.
• Line break after the closing brace, only if that brace terminates a statement or terminates
the body of a method, constructor, or named class. For example, there is no line break
after the brace if it is followed by else or a comma.

2.4 Lists [Format.Lists]


(2.4)
1 Lists shall be declared as List<TypeName> rather than TypeName[]
The reason for this is that Lists in Apex are backed by a java type conforming to List<T>.
Because List<T> does not have the runtime semantics of an array and the actual underlying
implementation complexity is unspecified it is best to use the List<T> syntax to remind the
developer that operations on a List may not be O(1) operations.

2.5 Variable Declaration [Format.Decl]


(2.5)
1 Variables shall be declared at the most inner scope that it is possible to use them
2 If a variable of identical name is used in another block the variable should not be declared outside
both blocks, rather a new declaration should be used for each block
3 Variable names shall describe what the data stored within is
Example:
Map<Id, Account> accountsById =
new Map<Id, Account>([SELECT Id, Name FROM Account]);
4 Variables shall not be single character unless they are iteration variables.
A variable name should represent the data and significance of what is being stored within.
Single letter variable names provide no context or meaning to the reader and thus fails to self
document.
[ Note: This requirement is relaxed for specific math code where the names of the variables
match the equation or system being implemented. However, a developer shall indicate exactly
what is being implemented. This is so that any developers that examine that code can quickly
recognize the purpose of the code. — end note ]
5 Acronyms should be avoided as variable names.
Acronyms obfuscate their meaning unless the reader is familiar with the concept. Because
this requires outside context that is not available within the code acronyms should be avoided.
However sometimes a client will use an acronym as part of their business process language. In
these cases in order to match the language of the business the use of an acronym may be required.
However, a developer shall document what the acronym means within the file in such cases.
6 Hungarian Notation shall not be used.
Hungarian Notation, while useful in certain contexts, requires that a developer is familiar
with it. Moreover the context it provides is less necessary in a strongly typed language like
Apex. Because the prefixes can be arcane and require foreknowledge they shall not be used in
Apex code. Rather a developer should favor self documenting variable names that indicate the
the data and significance of a variable.
7 Member variables should be marked as final if possible.
Mutability makes reasoning about the lifetime of a reference difficult and thus requires that
the reference is checked for null before each use. By marking a member variable final this
ensures that the reference is only assigned once. A Developer can then remove unnecessary null
checks as they can reason that a member variable may never be null.

2.6 Loops [Format.Loops]


(2.6)
1 A for each loop (for(<Type> variableName : <collection>) is preferred for iteration
whenever possible.
A foreach loop is preferred because it cannot possibly go over bounds and simplifies loop
iteration. It is not however the only way to iterate collections. Developers should not go out
of their way to use a foreach loop in such cases as it does not make sense: iterating multiple
collections at once, where an index variable is required, and data creation.
2 When using a for loop with an iteration variable, a developer shall seek to minimize subscript
access to values.
The complexity of Map.get() and List.operator[] is not clear. Because Apex is a
reference based language it is best to grab a single reference and operate on the reference as long
as possible. Moreover it clarifies exactly what the data being operated on is.
3 If a value needs to be accessed more than once in the body of the loop it should be cached at
first use

Example:
Bad:
for(Integer i = 0; i < foo.size(); ++i){
foo[i].bar();
foo[i].foobar();
}

Good:
for(Integer i = 0; i < foo.size(); ++i){
Barfoo fooItem = foo[i];
fooItem.bar();
fooItem.foobar();
}
4 Loops shall not contain:
• DML Statements
• SOQL
• SOSL
• @future methods
• Web Service callouts
• Methods that invoke any of the aforementioned
The aforementioned are all governor limited resources and should be used sparingly. Code
should be written in bulk safe ways, using a governor limited resource prevents this and creates
artificial limits on performance which are unnecessary.
[ Note: This requirement is relaxed if and only if there is no other possible implementation
of the requirement. In general a developer should push back on any requirement that can only
be implemented in such a manner. — end note ]
5 If a developer is required for the purposes of implementation to violate the prior prohibition, the
developer shall write the code in such a way as to fail gracefully as soon as the developer can
verify that the limits will be breached.
This shall mean one of the following:
• Failing as soon as the developer can calculate that limits will be breached if execution is
attempted.
• Failing during execution right before the limit is reached, allowing for partial success.
• Ignoring data that would result in a breach of the limits
• Using Apex Queuable to separate the data out into chunks that will not violate the limits.

2.7 Containers [Format.Containers]


(2.7)

2.7.1 Container Declaration [Format.Containers.Decl]


(2.7.1)
1 When instantiating a container that does not change the declaration should use the static con-
tainer initialization syntax.
Using the initialization syntax makes it clearer what is in the container and removes unnec-
essary boilerplate.
Example:
final List<Foo> foos = new List<Foo>{
new Foo(1),
new Foo(2),
};

final Set<Foo> uniqueFoos = new Set<Foo>{


new Foo(1),
new Foo(2),
};

final Map<String, Foo> foosById = new Map<String, Foo>{


’a’ => new Foo(1),
’b’ => new Foo(2),
};

2.8 Static Variables [Format.Statics]


(2.8)
1 Variables declared static should, in general, also be marked final

2.9 Comments [Format.Comments]


(2.9)
Comments are an essential part of documenting code, however the single source of truth is
always the code itself.
1 Comments shall follow the Apex Docs format in all cases to ensure that documentation may be
generated.
2 A Developer shall write self-documenting code and should not rely solely on comments to explain
their work.
3 A Developer should be wary of existing documentation comments, and should assume they are
advisory only and most likely invalid.

2.9.1 File Headers [Format.Comments.Headers]


(2.9.1)
This section is considered advisory for CBU, recommended for EBU, and compulsory for
SBU.
1 The accuracy of the comment shall be verified in code review after the file is changed.
2 Each file shall have a header of the following format
/**
* @author <Developer Name>
* @date <created date>
*
* @group <The Project name, this should be consistently
* spelled in all files>
*
* @description <Description of the functionality this code provides in
* the context of the larger project>
*/
2.9.2 Method Documentation [Format.Comments.Methods]
(2.9.2)
This section is considered advisory for CBU, recommended for EBU, and compulsory for
SBU.
1 Each public method shall be documented.
2 The accuracy of the comment shall be verified in code review after the method is changed.
3 The method comment shall describe the functionality and side effects of calling the methods.
The comment shall also describe each parameter.
4 The comment shall be of the form:
/**
* @description <a description of the method functionality>
* @param <param name> <description of parameter>
* @return <return value description>
*/

2.9.3 Property Documentation [Format.Comments.Properties]


(2.9.3)
This section is considered advisory for CBU, recommended for EBU, and compulsory for
SBU.
1 Every public property shall be documented.
2 The accuracy of the comment shall be verified in code review if the behavior of the property is
changed.
3 The documentation for the property shall explain any side effects of reading or writing to the
property if applicable.
4 The comment shall be of the form:
/**
* @description <description of the property and side effects>
*/

A developer should try to ensure that they keep mutability of references to a minimum. This
assists in reasoning about the lifetime of a static and allows a developer to remove unnecessary
null checks.

2.10 Unused Code [Format.Unused]


(2.10)
1 Unused code shall be expediently deleted.
Unused code only serves to create confusion in the org and serves no purpose. If the code is
needed again it exists in source control and can be retrieved. Moreover keeping the code means
the code must be maintained in the org and tested. This is unnecessary and uses the limited
amount of code each org has available. As such it is a developer’s duty to remove any code they
can verify serves no purpose in the org.
[ Note: Not all ‘duplicated code’ is actually duplicated. A developer should verify that the
code they are seeking to remove is actually duplicated and does not have subtle changes in
behavior. — end note ]
2 A developer shall not introduce unused code for the purposes of driving up code coverage.
Introducing reusable assets or tools into an org in an effort to drive up code coverage while
a short term solution does not actually fix the larger issue. Rather a developer should fix the
main issue, the lack of coverage in client code, than attempt to patch over it by introducing code
that has no purpose in the org.

2.11 SOQL Queries [Format.SOQL]


(2.11)
1 Keywords in SOQL queries shall be capitalized.
Capitalizing the keywords makes it easier to read the parts of the query individually.

Example:
List<Account> accountsWithNewOwners =
[SELECT
Name,
OwnerId,
MailingAddress
FROM Account
WHERE Owner_Changed__c = true];
2 A Developer should not include Id in the list of fields queried for unless it is the only field being
queried for.
The Id field is always included with the results of every query, explicitly adding it is unnec-
essary in Apex.
3 A Developer shall not query for records that that will only be used for update.
If the related records do not have data that is necessary to use as part of the update it is
not necessary to query for the record to ensure it is in the database unless the lookup field is a
"Pseudo lookup" (18 character string field). Because the Salesforce database is a true relational
store, lookup fields to deleted records will be nulled out automatically if the related record is
deleted. The practice of querying for Ids for related records to get a valid sObject has been
unnecessary for a long time. Instead create a new object of the type to update, set Id to the
value of the object to update and then update the necessary fields.

Example:
final List<Account> accountsForAddressMirroring = new List<Account>();
for(Contact updatedContact : updatedContacts){
accountsForAddressMirroring.add(
new Account(
Id = updatedContact.AccountId,
MailingStreet = updatedContact.Street,
MailingState = updatedContact.State,
MailingPostalCode = updatedContact.PostalCode
MailingCountry = updatedContact.Country,
MailingCity = updatedContact.City
)
);
}

if(accountsForAddressMirroring.isEmpty()){
return;
}

update accountsForAddressMirroring;
Chapter 3

Implementation Standards [Impl]

(3)

3.1 Implementation Complexity [Impl.Complexity]


(3.1)
1 A developer shall favor solutions with the minimal Big O runtime
2 Solutions with Non-Polynomial Runtime (N P ) runtime shall be rejected for n > 2
3 Solutions with runtimes of O(n2 ) should be rejected as they may cause CPU timeouts

3.2 Dataflow [Impl.Dataflow]


(3.2)
1 A Developer should avoid non-linear dataflow and the use of static variables to hold mutable
state
Non-Linear dataflow is when the flow of data through the application does not follow an easily
followable path. This happens because the developer assigns data to locations outside of the
linear flow of the application such as variables with static duration or member variables. This
dataflow pattern prevents a developer from reasoning about a variable’s lifetime and mutability.
This results in a developer having to code very defensively or experience unanticipated errors.
Furthermore it results in unnecessary heap and code bloat, as the variable cannot be released
until the enclosing scope or context is ended.
2 Data should be passed directly to a method as parameters or should be accessed from non-
static member variables if using a fluent interface

3.3 Classes [Impl.Classes]


(3.3)

3.3.1 Sharing Declaration [Impl.Classes.Sharing]


(3.3.1)
1 Classes shall always declare if they are with sharing or without sharing
Sharing is explicitly declared because security should never be left to chance. By declaring
sharing the developer makes their intent clear for that class.

9
[ Note: This requirement is relaxed if and only if the class is a utility that needs to inherit
sharing from the calling context. — end note ]

3.3.1.1 Sharing Trampolines [Impl.Classes.Sharing.Trampoline]


(3.3.1.1)
1 If a developer needs to access a without sharing (3.3.1) context from a with sharing class
they should use a trampoline.
A Trampoline is a private child class that allows access within the code only to the resource
needed.

Example:
public with sharing class AccountTeamServices{
// do with sharing stuff Here

private without sharing class AccountTeamNewMembersTrampoline{


List<Account> getAccountsForTeamMembers(
Set<Id> teamMemberUserIds){
return
[SELECT
Id,
Name
FROM Account
WHERE OwnerId IN :teamMemberUserIds];
}
}

public void extendSharingToAllTeamMembers(){


Set<Id> otherAccounts =
Pluck.ids(
new AccountTeamNewMembersTrampoline()
.getAccountsForTeamMembers(
Pluck.ids(
AccountTeamMember.UserId,
teamMembers)
));

// Finish adding shares here


}

3.3.2 Class Access [Impl.Classes.Access]


(3.3.2)
1 Service classes and Controllers shall have public access
2 Web-Service classes shall have global access.
3 Test classes shall have private access.
4 A developer may choose to not specify private access on a class or method explicitly. Apex
defaults to private by default, however some tools may require the keyword for proper syntax
highlighting.

3.3.3 Class Lifetime [Impl.Classes.Lifetime]


(3.3.3)
1 Instances of Classes should be preferred so that lifetimes and dataflow (3.2) are clear.
2 The use of static methods should be avoided unless the methods are Pure Functions, or required
by Apex for implementation (ex: @auraEnabled methods).

3.3.4 Inheritance [Impl.Classes.Inheritance]


(3.3.4)
Inheritance is when a class or interface derives from or extends another class or interface.
Inheritance is an extremely powerful tool as it allows for direct extension of base functionality, or
to extend the requirements of an interface to be more comprehensive. However inheritance shall
always follow the Liskov Substitution Principle. A developer should be aware that inheritance is
an explicit IS-A relationship between parent and child.
The Liskov Substitution Principle is defined as:

Subtype Requirement: Let φ(x) be a property provable about objects x of type T .


Then φ(y) should be true for objects y of type S where S is a subtype of T .[1]

This means that any super-type should be replaceable with any of its subtypes without the
calling code needing to alter behavior; on the basis that the visible side effects to the caller
should not change.
1 Classes that implement an interface or extend an abstract class shall follow the Liskov Substitution
Principle.
2 In general a developer shall favor composition over inheritance when extending behaviors.
The IS-A relationship enforced by inheritance is often not desired. Composition however
reflects a HAS-A relationship which is far more common. By using composition instead of
inheritance a developer makes the relationships between parent and child clear.

3.3.4.1 Interface Inheritance [Impl.Classes.Inheritance.Interfaces]


(3.3.4.1)
1 Interfaces shall only inherit from another interface if there is an absolute IS-A relationship
between the parent and child.
In general an interface should be as focused as possible. There is a temptation to make an
interface that spans multiple purposes. This should be avoided because it leads to implementation
bloat.

3.3.4.2 Abstract Classes [Impl.Classes.Inheritance.Abstract]


(3.3.4.2)
1 Abstract classes shall have implementation. If not they should be converted to an interface.
If there is no implementation for an Abstract Class to inherit from an interface should be
used instead. An interface is lighter weight and has fewer restrictions.
3.4 Prohibitions [Impl.Prohibited]
(3.4)
1 Code SHALL NOT under any circumstances be written for the purpose of purely increasing
code coverage. Any code that exists purely to increase code coverage shall be removed from the
org as soon as it is possible to do so, along with any accompanying tests.
Implementing a code coverage cheater may cause Bluewolf’s partner status to be
revoked and will reflect very badly on the developer responsible.

Example:
static testMethod void coverage(){
foo();
}

// implementation coverage cheater


void foo(){
Integer i = 0;
i +=1;
i +=1;
i +=1;
i +=1;
i +=1;
i +=1;
i +=1;
//...(n times)
i +=1;
}

This code does nothing and tests nothing. As such it exists purely to drive up the reported
code coverage in the organization. Implementing such code is a disservice to our clients as it
creates the illusion of reliability and a well tested codebase.
trigger

handler

ServiceA ServiceB

Figure 3.1: Trigger Dispatch

3.5 Triggers [Impl.Trigger]


(3.5)
1 A developer shall only create one trigger per object
Because the order in which triggers are fired is undefined, having a single trigger on an object
makes order of operations predictable and debuggable.
2 A trigger should be named as the object e.g. Account.trigger
Case is an explicit exception; due to language restrictions it should be named CaseTrig-
ger.trigger
3 A developer should seek to combine triggers when possible
[ Note: Write regression tests prior to doing any changes. — end note ]

3.5.1 Trigger Handlers [Impl.Trigger.Handler]


(3.5.1)
1 Trigger handlers should be used for all triggers.
Trigger handlers make delegation of work easier, they also make order of operations more
explicit. Using a trigger handler also means that any business logic that leaks into a trigger
handler is performed with sharing. Furthermore the bypassTrigger boolean allows for the
trigger to be unconditionally bypassed for testing.
Example:
Trigger:
trigger Foo on Foo__c (before insert, after update) {
FooTriggerHandler handler =
new FooTriggerHandler(Trigger.new, Trigger.oldMap);
if(Trigger.isBefore) {
if (Trigger.isInsert) { handler.beforeInsert(); }
}else if(Trigger.isAfter) {
if (Trigger.isUpdate) { handler.afterUpdate(); }
}
}

Handler:
public with sharing class FooTriggerHandler{
// used for testing triggers when the
// setup DML would interfere with the tests
@testVisible static Boolean bypassTrigger = false;
final List<Foo__c> newRecords;
final Map<Id, Foo__c> oldRecords;

public FooTriggerHandler(
List<Foo__c> newRecords,
Map<Id, Foo__c> oldRecords) {
this.newRecords = newRecords;
this.oldRecords = oldRecords;
}

public void beforeInsert(){


if(bypassTrigger){return;}
// do before insert dispatch here
}

public void afterUpdate() {


if(bypassTrigger){return;}
// do after update dispatch here
}

// Other trigger actions defined here


}
2 Trigger handlers shall contain no business logic. Trigger handlers should dispatch to services to
preform business logic.
Allowed operations in a trigger handler:

• Dispatch to a service class


• Invoke a filter returned from or on a service
3 Trigger handlers shall be with sharing (3.3.1)
This is to prevent any business logic that leaks in will be preformed with security enabled.
Whereas a trigger itself is without sharing meaning that any logic preformed within may violate
security constraints.
4 The bypassTrigger check shall precede any logic in a trigger handler.
This is to allow a trigger to be bypassed even if there is a custom setting check that does the
same thing. By putting the check at the top of the action we can ensure that bypass behavior is
not dependent on external settings or configuration.
3.6 API Design Guidelines [Impl.API]
(3.6)

3.6.1 Parameter Guidelines [Impl.API.Parameters]


(3.6.1)

3.6.1.1 Boolean Parameters [Impl.API.Parameters.Booleans]


(3.6.1.1)
1 Developers shall not create methods with boolean parameters. Such parameters are called
Boolean Traps. A boolean trap is called such because it obfuscates the meaning of the pa-
rameter at the call site. Because of the high likelihood of misinterpretation the use of enums for
clarity is preferred.
A boolean trap is a boolean parameter for a method that provides no context or clear meaning
at the call site of what that boolean means. This reduces readability as it requires the reader to
find documentation of the method to ascertain what the boolean means.

Example of boolean traps:

// A common TestUtils signature


List<Account> createAccounts(Boolean doInsert);

/* Example of why this creates readability issues:


* The line below provides very little information as to
* what false means in this context. For this to be readable
* a developer would have to annotate the parameter.
*/
List<Account> parentAccounts = createAccounts(false);

/* A common boolean trap that is part of the standard API


* is deepClone on List<T>. As demonstrated below it contains
* three boolean parameters which provide no useful meaning
* at the call site.
*/
List<Account> childAccounts =
parentAccounts.deepClone(false, true, true);

Example of a property solution:


public enum SortMode{
Ascending,
Descending
}

class Sorter{
Boolean ascendingSort = false;

/**
* @description Controls how the data is sorted as set
* by the SortMode enumeration
*/
public SortMode sortingMode{
get;
set{
ascendingSort = sortingMode == SortingMode.Ascending;
}
}

// sorting logic here


}

// elsewhere in the code

/**
* @description Gets the names of the contacts in ascending order.
* Pure, has no side effects.
* @return The contacts’ full names in descending order
*/
public List<String> getNamesAscending(){
Sorter sort = new Sorter();
sort.sortingMode = SortMode.Descending;
return sort.doSort(names);
}

Another solution to the boolean trap problem is to split the method into two signatures that
indicate the difference in functionality.

Example of an overload solution:


/**
* @description Builds and inserts test data records into
* the database
* @param recordCount The number of records to build and insert
* @param objectType The sObjectType of the records to build
* and insert
* @param fieldToValue A map of sObjectField to the value or
* value provider to override the default fields provided in
* the RequiredFieldsCache
* @return The list of built and inserted objects
*/
public static List<SObject> create(
Integer recordCount,
Schema.SObjectType objectType,
Map<Schema.SObjectField, Object> fieldToValue) {
List<SObject> records =
build(
recordCount,
objectType,
fieldToValue);

// insertion and error handling here


return records;
}

/**
* @description Builds test data records
* @param recordCount The number of records to build
* @param objectType The sObjectType of the records to build
* @param fieldToValue A map of sObjectField to the value or
* value provider to override the default fields provided in
* the RequiredFieldsCache
* @return The list of built objects that are not yet inserted
*/
public static List<SObject> build(
Integer recordCount,
Schema.SObjectType objectType,
Map<Schema.SObjectField, Object> fieldToValue){
// build logic here
}
2 In the case where an existing method with a Boolean Trap (3.6.1.1) must be called the developer
shall annotate the parameter to describe the boolean parameter.

Example:
List<Database.SaveResult> results = createObjects(false /*all or none*/);
Chapter 4

Unit Testing [Testing]

(4)
The following sections are relevant specifically to Apex Testing and are supplementary to any
guide provided in the Developer Guide.

4.1 Basics [Testing.Basics]


(4.1)
1 All tests should have a Test.startTest() and Test.stopTest().
These reset the limits and allow for a better understanding of the runtime behavior charac-
teristics of the code, as well as testing against the Apex runtime limits.
2 The number of lines of code inside Test.startTest() and Test.stopTest() should be
minimal
In keeping with the aforementioned goal only lines that are under test or fixtures needed for
verification should be in the Test.startTest() and Test.stopTest() ‘block’.

Example trigger happy path test:


List<Foo__c> newFoos =
SObjectFactory.build(Foo__c.sObjectType, NUM_TEST_RECORDS);

Test.startTest();
insert newFoos;
Test.stopTest();

// verify results here

4.2 Code Coverage [Testing.Coverage]


(4.2)
1 A developer shall endeavor to have all of their code covered by unit tests.
2 Untestable code should be isolated as much as possible and kept to at most 1 line if possible.
Suggested techniques such as Inversion of Control and dependency injection should be used to
allow a developer to mock out the problematic lines

18
4.3 Goals of Unit Tests [Testing.Results]
(4.3)
1 Tests should be targeted at verifying behaviors dictated by requirements not implementation.
While there are cases where testing implementation details must be done, in general an imple-
mentation should not require this.

4.4 Test Assertions [Testing.Asserts]


(4.4)
1 Tests shall contain assertions
2 Test assertions shall verify the behavior the test declares to be verifying
3 Test assertions should have messages explaining what the assertion is and the expected outcome.
Tests serve as documentation of functionality, in many cases the best documentation. As such
it is important to make sure the tests are clear, concise, and explicit. Good assertion messages
help because they make it clear what the expected observable effects are that a developer or user
should be expecting.

Good Example:
System.assertEquals(
null,
foo,
’When an invalid parameter is passed the returned Foobar ’ +
’should be null’);

This assertion message is good because it explains what should trigger the expected behavior
and what the expected behavior is.

Bad Example:
System.assertEquals(
null,
foo,
’The returned Foobar should be null’);

This is a an example of a bad assertion message, it says the returned value should be null
but not why. Thus it fails to document what triggers this behavior. Documenting the why of a
behavior is important because it makes it clear what a developer should expect when interacting
with that API.
4 The size of a returned result list shall be verified prior to asserting on the contents

Example:
final Integer EXPECTED_NEW_ACCOUNTS = 5;

List<Account> createdAccounts =
[SELECT Name, Phone, ShippingStreet FROM Account];
System.assertEquals(
EXPECTED_NEW_ACCOUNTS,
createdAccounts.size(),
’The correct number of accounts should be created ’ +
’when a new customer is added’);
for(Account newAccount : createdAccounts){
// verify details of created objects here
}
4.5 Triggers [Testing.Triggers]
(4.5)
1 All triggers shall be tested using bulk data
This shall mean Limits.getLimitQueries() + 1 records
2 If there are legacy triggers that prevent the aforementioned limit from being used, then the
developer shall ascertain the upper bound on which the system can tolerate. This limit shall be
documented at the point where the bulk record count variable is defined.

Example:
// Current limit due to foo.trigger
static final NUM_TEST_RECORDS = 5;
// static final NUM_TEST_RECORDS = Limits.getLimitQueries() + 1
3 A Trigger Handler 3.5.1 is considered to be tested by the trigger tests and should not require its
own tests
Bibliography

[1] Barbra Liskov. “A behavioral notion of subtyping.” In: ACM Trans. Program. Lang. Syst.
16.6 (), pp. 1811–1841.

21

You might also like