Deep Dive into Debugging Apex and Visualforce Code with New Force.Com Ide
Learn more about different ways to debug Apex and Visualforce code using Jetforcer, a brand-new IDE based on IntelliJ IDEA
Join the DZone community and get the full member experience.
Join For FreeIntroduction
This post is a short overview of how to debug Apex and Visualforce code in the JetForcer, a new modern Force.com IDE based on IntelliJ IDEA platform.
We’ll take a closer look at all available features and consider a few tricks that make debugging more easy and handy. For this, we'll construct a simple debugging puzzle and try to fix it.
Let's go!
Step 1: Prepare Org Data
In brief, our aim is to create a Visualforce page that will display available accounts and provide an ability to filter them. First of all, fill our org with the necessary accounts. For this, run following code in the Execute Anonymous tool window or just use existing accounts.
Anonymous code:
Map<String, String> acctName2RatingMap = new Map<String, String>{
'Ant Conglomerate' => 'Warm',
'Bee Collection Agency' => 'Cold',
'Beetle Brothers Body Shop' => 'Hot',
'Butterfly Beauty Supplies' => 'Cold',
'Flea LLC' => 'Cold',
'Fly Airlines' => 'Warm',
'Moth Candle Company' => 'Hot',
'Tick Timepieces' => 'Hot',
'Wasp Industrial Products' => 'Cold',
'Weevil Consultancy' => 'Warm'
};
List<Account> newAccts = new List<Account>();
for(String accName : acctName2RatingMap.keySet()) {
Account newAcct = new Account();
newAcct.Name = accName;
newAcct.BillingCity = 'Suffragette City';
newAcct.Rating = acctName2RatingMap.get(accName);
newAccts.add(newAcct);
}
for (Account each : newAccts) System.debug(each);
insert newAccts;
Step 2: Visualforce Controller
Next, let’s create a Visualforce controller to debug. It should be able to filter accounts list if needed or just return original data without filtration.
Desired class content:
public class AccountViewerController {
public Boolean removeCold {get; set;}
public List<Account> results {get; set;}
public AccountViewerController() {
removeCold = false;
results = [SELECT Id, Name, Owner.Name, Rating, BillingCity, BillingState
FROM Account
WHERE BillingCity = 'Suffragette City'
ORDER BY Name ASC];
}
public List<Account> getAccountTable() {
List<Account> accountsToReturn = new List<Account>(results);
if (Boolean.valueOf(removeCold)) {
removeColdAccounts(accountsToReturn);
}
return accountsToReturn;
}
public void removeColdAccounts(List<Account> listToReduce) {
System.debug('Removing "cold" accounts');
System.debug(' size before: ' + listToReduce.size());
for (Integer i = 0; i < listToReduce.size(); i++) {
Account a = listToReduce.get(i);
if (a.Rating.equalsIgnoreCase('Cold')) {
listToReduce.remove(i);
System.debug('removed cold account: ' + a.Name);
}
}
System.debug(' size after: ' + listToReduce.size());
}
public void noOp() {
}
}
Step 3: Visualforce page
Now we need a Visualforce page to view and filter our accounts.
Page markup:
<apex:page controller="AccountViewerController">
<apex:form >
<apex:outputPanel id="resultTable">
<apex:pageBlock >
<apex:actionstatus id="status">
<apex:facet name="start">
<div class="waitingSearchDiv" id="el_loading"
style="background-color: #fbfbfb; height: 100%; opacity:0.65;width:100%;">
<div class="waitingHolder" style="top: 74.2px; width: 91px;">
<img class="waitingImage" src="/img/loading.gif" title="Please Wait..."/>
<span class="waitingDescription">Please Wait...</span>
</div>
</div>
</apex:facet>
</apex:actionstatus>
<apex:pageBlockSection title="Accounts Filter" collapsible="false">
<apex:inputCheckbox value="{!removeCold}" label="Hide Cold Accounts">
<apex:actionSupport event="onchange" action="{!noOp}" status="status" rerender="resultTable"/>
</apex:inputCheckbox>
</apex:pageBlockSection>
<apex:pageBlockSection title="Scheduled Jobs" collapsible="false">
<apex:pageBlockTable value="{!accountTable}" var="a" id="thePageBlockTable">
<apex:column style="vertical-align:top">
<apex:outputField value="{!a.name}"/>
<apex:facet name="header">Name</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!a.BillingCity}"/>
<apex:facet name="header">City</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!a.BillingState}"/>
<apex:facet name="header">State</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!a.Rating}"/>
<apex:facet name="header">Rating</apex:facet>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:outputPanel>
</apex:form>
</apex:page>
Step 4: Configure Trace Flags
Next, configure trace flag for our Visualforce controller. It necessary for generation debug logs after controller activity. For trace flags management, JetForcer provides the Trace Flags tab in the Log Manager tool window.
Please note that there are few essentials for successful debugging:
Debug log should contain
STATEMENT_EXECUTE
events which used for navigating to sources. (Debug level for Apex log category should be at least "Finer")[Optional] If you want to explore local variables, fields or constants with Variables View, then debug log should contain
VARIABLE_ASSIGNMENT
events (Debug level for Apex log category should be at least "Finest")[Optional] If you want to debug Visualforce controllers, then debug log should contain
VF_APEX_CALL_START
events which used for calculating current source position. (Debug log for Visualforce log category should be at least "Fine")
Step 5: Identify a Problem
Our expectations are that our Visualforce page should be working perfectly! Let’s make sure.
Open page in a browser;
Notice that your accounts list includes cold accounts.
Select “Hide Cold Accounts”.
As we can see, something went wrong and one “Cold” account is still persisted in the list. Why did this happen? It’s time for debugging!
Step 6: Find Log with Log Manager
After our activity in the browser, a corresponding debug log has been generated. Find it by using the Log tab from the Log Manager tool window.
This tab is a powerful tool for managing debug logs and provides following features:
Retrieving new logs from the server by using the Retrieve Last Logs action;
Applying filters to logs table (e.g. filter all logs by specific Operation);
Distinguish between read/unread/new logs (e.g. new logs are shown in bold);
Starting a new debug session or creating new debug configuration for selected log right from the Logs tab.
Step 7: Use Debug Watches for Exploring Significant Parts of Your Code.
JetForcer provides Debug Watches to a handy exploring of code expressions values (variables, fields, method calls etc.) in the Variables View.
Debug Watch is a System#debug(Object) method call with a specific pattern:
System.debug('[<watch_name>::<watch_type>]:watches' + JSON.serialize(<expression>))
We can avoid "List of size 10 too large to display" warning by using watches.
Feel free to quickly add new watch by using "Add to Watches..." intention action.
After adding watches you should deploy changes to the org and generate new debug logs according to new sources.
Step 8: Stepping Through the Code Execution Flow (Step Actions and Breakpoints).
JetForcer provides several methods of navigating through code while debugging:
Step Actions: When a debug session is started, the Debug tool window becomes active and enables you to get control over the code flow execution by using Step Actions.
They can be called from the Run menu in the main toolbar, or by using the actions on the stepping toolbar in the Debug tool window.
Each stepping action advances the suspend context to the next STATEMENT_EXECUTE event location, depending on the action you choose.
Breakpoints are source code markers used to intentional suspending place in a code execution flow. Typically used for quickly jump to the desired STATEMENT_EXECUTE event by skipping all previous data. For adding or removing a breakpoint, click the left gutter area at a line where you want to toggle a breakpoint.
Step 8: Debug the Problem
The problem is that removing "Cold" account from the list would change the positions of the subsequent accounts. It shifts them one position up.
So if you have two consecutive "Cold" accounts, the second one would be always skipped by the filter.
Step 9: Fix the Problem
To fix the problem just decrement the value of the loop's counter "i", each time removing an account from the list.
public void removeColdAccounts(List<Account> listToReduce) {
System.debug('Removing "cold" accounts');
System.debug(' size before: ' + listToReduce.size());
for (Integer i = 0; i < listToReduce.size(); i++) {
System.debug('[listToReduce-BEFORE::List<Account>]:watches' + JSON.serialize(listToReduce));
Account a = listToReduce.get(i);
if (a.Rating.equalsIgnoreCase('Cold')) {
listToReduce.remove(i);
i--; // add fix
System.debug('[listToReduce-AFTER::List<Account>]:watches' + JSON.serialize(listToReduce));
System.debug('removed cold account: ' + a.Name);
}
}
...
That's About It!
Here is a very concise overview of some JetForcer debugger capabilities, just to give you a quick start.
If you find it interesting, you may try out a free 30-day trial version from the official website.
Make sure you follow @JetForcer on Twitter to be notified about updates, features, new releases and interesting facts about the JetForcer plugin.
Enjoy developing with JetForcer and JetBrains IDEs!
Opinions expressed by DZone contributors are their own.
Comments