Pages

Monday, February 29, 2016

General Tips For Oracle PL/SQL Developers Switching To Salesforce

Here are some notes from the trenches on what it's like to try to do switch from Oracle PL/SQL to Apex for your anonymous database modification code & stored procedure & trigger development:

  1. Trigger-writing in Salesforce essentially means "surrounding a crippled version of SQL in a crippled version of Java."
    It's definitely doable, but a pretty big paradigm shift from Oracle PL/SQL trigger programming and a bit of a shock to the system at first.
     
  2. You can't build as complicated of "SOQL" queries in Salesforce as you can build "SQL" queries in Oracle.
    • The "Salesforcey" way around this is typically to cache the results of simpler "SOQL" queries in Java-like "List" & "Map" & "Set" data structures and then use Java-like code to loop through the cached data and finish off the aggregations + filters + joins.
       
  3. Some "SOQL" queries can be made more elegant by putting half of their complexity into the definition of a "formula field" on a given object - but the downside is that you now have your code split between the trigger code and the definitions of the tables themselves. Nevertheless, for example, rather than hard-code this logic into the trigger, you could add a "is SAT verbal score greater than 600 and SAT math score greater than 500?" Boolean formula field to the "Test Score" table in Salesforce and just have the trigger check it for "==TRUE."
     
  4. Although you'll be writing in Java-like code, except when you're creating "reference" objects (like here), you won't be doing a lot of "object-oriented" programming.
    Mostly, you'll be defining functions and writing step-by-step procedural code that calls them.
    In code, this will play out as most of your "classes" and "methods" having the modifier "static."
    So don't worry too much about not being experienced at object-oriented programming.
     
  5. Just as you can write "stored procedures" but not attach them to a database trigger in PL/SQL, in Salesforce you can save snippets of code that you're not sure how often you'll actually need to use (but may suddenly need to invoke on demand) in Java-like "classes."
     
  6. Just as you can execute "anonymous procedures" in PL/SQL, in Salesforce you can anonymously execute lines of Java-around-SQL-like code. This is often useful for one-off data fixes that are too complicated for "export, fix, & re-import" fixes with Data Loader or "MassImpact" fixes with DemandTools.
     
  7. "Export, fix, & re-import" fixes are often made much faster by doing the "fix" step with Python rather than Excel.
    • Anyone responsible for such work will find it handy to have a Python environment + the "NumPy" & "Pandas" & "CSV" plugins installed on their computer and an IDE for Python code execution (the "Spyder IDE" / "WinPython" environment works well for me).
       
  8. Get a copy of Dan Appleman's "Advanced Apex Programming" to get more "how to make your code efficient" tips and to learn subtle "variable scope" differences in Salesforce's Java variation. Also handy is a StackOverflow account, because we have our own sub-board at salesforce.stackexchange.com.

Friday, February 12, 2016

Job-Hunting Advice For International Students

And now for something a little different (not Salesforce).

I've worked a lot with international students and, upon helping a few at a career fair last night, realized I'd like to formally publish the advice I often give international students about job-hunting.

It seems that colleges and universities are able to perform superhuman feats of service welcoming international students, helping them succeed socially and academically, making sure they know what they can and can't do for work, and steering them toward job-search resources (plus making them aware of timelines and deadlines).

But there are a few "mental barriers" I think often remain.

Many people have helped me get through my "but I don't know what I'm doing..." mental barriers, so I hope I can "pay it forward" somehow.

---

I'd say most mental barriers unique to international students can be summarized as:

"I'm afraid of being judged for my handicaps, and history shows that I have good reason for that!"

And all the solutions I suggest can be summarized as:

"Jump ahead of the company and guide their staff.  Talk about your handicap - making clear not only what it is but what it isn't (so their imaginations don't run wild) - and present a solution to the problems it poses BEFORE they have time to make their own guesses.

"You're right that by the time they've assumed, it's often too late to change their minds.  But you're wrong if you think you can't change their minds at all.  You just have to be one step ahead of them.

"It's easy to presume a company's staff make all hiring judgment calls on their own just because they are in a position of authority to do so, but that's a myth.  They know what expertise they don't have.  You will be amazed how open-minded and grateful hiring officials are about being told how to handle your handicap."

"It's hard, and it will take a lot of practice to get good at, but investing the time in getting good at 'making your own handicaps look like strengths' will drastically change your job-hunting life forever.  It is the foundation on which everything else you're going to get training and practice for as you job-hunt (networking, cold-calls, resumes, cover letters, interviews) will become more successful."

---

I suppose I haven't yet said anything that isn't true for domestic students, too ... but what I think is unique about international-student-specific mental barriers is that they ALL seem to be variations on this theme.  So, below are 6 specific complaints of this type that I frequently hear international students worry about and the 6 specific 'So make it sound like a good thing' advice I give them.



========



CONCERN #1:
"Every company I'm applying to for after-college work says they don't want to talk to people who aren't US citizens or permanent residents because they don't have any money to sponsor people for work visas."

ADVICE:
You're right that they're judging you as soon as they see your immigration status - so the best you can do to 'jump ahead' of that is in your cover letter / resume.  Some companies legitimately don't want to work with F-1 students, but in my experience, most companies simply don't know the difference.  Here's something you can throw in as a footnote to your cover letter / resume if you're explicitly getting shut down by companies with, 'No sponsorships!':
'Over the summers and for [12 / 12-27 (if STEM)] months after graduation, I am authorized to do work in America that gives me real-world experience extending my academic training in [list majors] at no cost to your company.  Hiring a current or recently-graduated international student is different than sponsorship (which pertains to long-term work visas) and does not require any complicated legal filings or fees.  My work for your company would only be constrained by duration and subject area - hiring me can be structured as an internship, a temporary position, or even a direct hire.  Please ask me if you have any questions!'
Or, here's one that's concise and a little less bold, if it's true of you:
'I am an international student from [country] who intends to establish a career in my home country.  My visa allows me to perform [major]-related work during the course of my studies and up to [one / two and a half (STEM)] year[s] after graduation.'



CONCERN #2:
"Everyone I want to work for says they don't offer internships."

ADVICE:
Stop saying the word 'internship.'  There's no requirement that your work has to be an 'internship.'

You've probably heard that word a lot because 'internship' means short-term work that is structured to help the person performing it gain specific skills in a field.  Obviously, this kind of position naturally suits the limitations on the kind of work you can perform as an international student, so everyone talks about internships a lot.

But you can gain relevant skills at a job even if the job isn't specifically structured around a skill-building goal.

You can also work jobs that are a 'temporary' position or a 'direct hire,' as long as you stay within the duration, subject-matter, and hours/week requirements of your F-1 visa status.  So broaden your job search beyond internships.

'Cold-call' relevant staff at companies you'd like to work with asking them if they ever have, or can create, 'short-term work' - rather than asking 'if they have internships' available.  You'll find that many of them who 'don't have interships' are open to 'having short-term help.'

Open yourself up to working with temp agencies (just make sure they understand your duration / subject / hours-per-week limits so they don't suggest inappropriate placements).

And here's a nice little phrase you can add to cold-call letters or your resume to ensure they don't make any assumptions about whether or not you're easy to hire:
'I am an international student from [country] looking for hands-on experience in America before embarking upon a career in my home country.  My visa allows me to perform [major]-related work during the course of my studies and up to [one / two and a half] year[s] after graduation.'



CONCERN #3:
"I have to get a job in my major, but I've never worked in my major - I worked in foodservice / RA / etc. on campus."

ADVICE:
If your old jobs aren't relevant to the one you want, give them just enough resume space to show that they existed.  They show you've been able to hold down a job and work hard, but don't waste space.

Create a space on your resume called 'Projects' or 'Projects and Research' or 'Projects and Field Work' - etc.

Include a 1-sentence description of a research paper you did.  Describe a class trip by what you did on it, e.g. 'Investigated the pH of lake sediment along Lake Superior boat docks.'  Etc.

The #1 thing to remember to be successful at this is to NEVER let the fact that everyone in the class had to do something, or the fact that the professor walked you through how to do something, stop you from mentioning it.  Your resume is about advertising YOU, and you really have done these things!

Include even things you couldn't repeat without guidance.  You're young.  If a company needs you to be able to do such work independently, they'll let you know at the interview stage.  But don't be afraid to list something you've done in class just because you felt like it was 'no big deal.'



CONCERN #4:
"I'm already handicapped in so many ways.  Why would anyone read my resume or my cover letter?"

ADVICE:
Because you took the time to write a great one.

Different companies put different values on the resume and cover letter, but here's a secret:  those who actually read every applicant's cover letter REALLY love cover letters and get a whole lot of generic ones they hate.

If you use your cover letter (and any free-text areas in the application form) to show that you actually bothered to read the job description (use the job description's words!), you will be in the top 50% of cover letters.

If you also follow up the company's own words with descriptions of why you are the candidate who can solve those needs, you will be in the top 5% of cover letters - including Americans applying to the same job.

Most people get too busy applying for every job they see to tell the story of why they personally fit the job in question.  Take the time and you will be at the top of the stack, despite your visa status, employment background, lack of experience with American customs, etc.



CONCERN #5:
"I'm already handicapped in so many ways.  How am I going to keep my nerves together in an interview when I fear people are looking for a reason to reject me?"

ADVICE:
Find your fears.  Google 'common interview questions' and pick 5 that look terrifying.  Or make up a few of your own about fears that linger despite perhaps having addressed them in your cover letter - e.g. your immigration status work restrictions.

Develop 'elevator-speech'-length answers to them (15-30 seconds).
Stand in front of the mirror and ask yourself the scary question, and practice delivering your answer at least 10 times (focusing on comfort and body language).

It doesn't matter if these aren't the questions you get asked.  They're the questions that brought you fear, and now they don't.  It's like lifting weights in the gym and then being asked to help a friend move a refrigerator.  The skill of 'calmly making yourself sound good in response to a question you were afraid to be asked' will be transferable when you are sitting in an interview.


CONCERN #6:
"I have to find a job in my field, which means I'm applying to jobs where they expect a lot of knowledge in my field.  What if they ask me some sort of obscure question I don't know the answer to?"

ADVICE:
Similar answer to the 'how am I going to keep my nerves together in an interview?' but with one addition:

Google to find 2-5 common interview questions in your field where you know enough about the subject to understand what is being asked, but doubt your abilities to give a top-notch answer to the exact phrasing.

Think about how you would explain what these questions are asking to your grandmother or to a 10-year-old.  Now bring it down to 15-30 seconds.

Now mirror-practice answering the question in a way that draws upon that 'for a 10-year-old' brief explanation of the issue at hand.

Phrase your answer something like this:
'I haven't had the opportunity to work with [______], but I know that it is a [_____] used for [______], and that when working with [_____]s, it is important to make sure that issues of [______, ______, and ______] are addressed because otherwise, [____, ____, and/or _____] could happen.'
Again, this 'mirror-practice' is transferable to unexpected questions.  You will now be experienced at analyzing a question beyond your expertise for what you DO know about its subject-matter and explaining how your general knowledge enables you to solve the company's problems, given time to further train on the specifics.

If you secure an interview, you might want to repeat this drill with 2 new questions from the internet that pertain to the job description at hand.


========

GOOD LUCK!!!!!!

:-) :-) :-)

-K



========



P.S.  1 MORE CONCERN:
"I think xenophobia / prejudice is hindering my job search."

ADVICE:
Yikes.  I'm really sorry.  That is terrible.

For the most part, I'm in over my head on that one.

But I do have one suggestion, and yes, it comes from the mind of someone who's never had had to deal with it, but...

Even if you want to just walk away, think about firing back your rejection reason at the people doing the hiring and ask them if they could clarify what specifically they think would put you in a stronger position to overcome that disadvantage in the future.

If you don't get a satisfactory answer and still think prejudice is an issue, tell the the HR department coordinating the job search about what was said to you and ask them if they have any clarification about what the company might be looking for, so that you can better prepare yourself for similar positions in the future.

(HR probably won't actually have a good answer.  You're mostly just playing stupid and, by 'asking for useful information,' letting HR know what their employees are saying to candidates that might be against corporate policy or the law.)


Example:
I worked with someone who wanted a customer-facing technology position, but whose only American jobs had been a summer internship and graduate-student-worker positions.

Multiple times, he was told that the company was 'looking for someone with more U.S. experience.'

He came away with an icky feeling that most of those meant, 'We don't want someone who's not only relatively new to the field, but also has brown skin and an accent, to be a major liaison with our customers.'

My only question was, 'But what if there's even one of them who doesn't really mean that?  Who would consider hiring you in a year?  Don't you want to know exactly what they want to see you've been doing for that year so you can think about spending your next year doing it?  It sounds like there's a lot of prejudice in your field, but maybe asking this question over and over can help you better prepare yourself for a job at the nonprejudiced companies.  And in the meantime, subtly tattle to HR on the prejudiced ones.  Oh, and also, I'm really sorry you keep running into this.'



Thursday, February 4, 2016

The Singleton Pattern And "Commonly Used Data"

If you hadn't noticed, Salesforce stored-procedure & trigger programming is basically using Java wrapped around crippled SQL. Which means it's a good idea to be up on your object-oriented design patterns.

One of those that I just used to avoid copying the same SOQL query into a 5th trigger-handler's source code is the Singleton pattern.
(I'll be going back and fixing the 4 older trigger-handlers. Yes, I let it get that bad before I had time to fix it.)


Quick refresher for newer programmers: in object-oriented programming, a "class" is a "cookie cutter" and an "object" is each individual cookie you cut. So, a "car cookie cutter" would say, "Make sure that every car has a number of wheels, a number of doors, a make, a model, a paint color, and a unique serial number. It also needs to be driveable." A "car object" would be the "the 4-wheel 4-door red Ford Taurus with serial #XYZABC12345" - these are the values stored in its "class-level" variables. Like all cars, it would be driveable (this is a "method" that's part of the definition of all "cars" and it comes with this particular car just because it's a car).

When you're defining Apex triggers, you typically don't actually do object-oriented programming with classes. Typically, you define & use "static" methods for the "car cookie cutter" and directly invoke the "drive()" method. Which is a little hard to imagine as a metaphor - driving the car factory instead of the car - so sorry about that. But just trust that, well, if it's not a method that actually needs a particular car to exist, such methods can be defined and used and that's probably what you're actually used to doing as an Apex trigger programmer.


But ... sometimes it IS useful to manufacture a particular car!

And, more specifically, if what you'd like to do is put a single car in a museum and call it "best car ever made" and have all of your code just copy its color and number of doors, you want a Singleton. The reason to do this "copy whatever's in the museum" pattern is that this keeps you from having to hard-code values like "silver" or "4-door." If you ever change your mind about these details, you just change the car in the museum.

Think of a "Singleton" class as the museum itself. It's not open to the public - all you can do is ask the guard, "What color is the car that's in there right now?" You don't even get to know whether they really have a car in there or not. You just know that the sign on the front door says you can ask the guard what color the car is, how many doors it has, etc.

(There's an extension on the "Singleton" programming pattern where, for efficiency, the museum doesn't even exist until the first visitor asks a tourist-info-kiosk for directions to it - at which point the city quickly scrambles to build a museum complete with "4-wheel 2-door silver Ford Taurus" inside before she gets there. Designing the museum this way is called "lazy instantiation." In my code below, I took this pattern a bit further for rare questions & named it "lazy data fetch" in my comments. Think of it like not bothering to paint the car until the first time someone asks the guard what color it is.)


So here's my code for accessing "commonly used info" like "the default Account ID for new Contacts," "the default Owner for records," "the Record Type ID of a given object & record-type-name," etc.

To call the "Singleton" code and ask it "the Record Type ID of a given object & record-type-name," I just write:

Id rtID = UtilityDefaultInfoOftenNeeded.getInstance().getRecordTypeId('Admissions', 'Opportunity');

The call to "getInstance()" asks for directions to the "useful settings" museum (at which point the city scrambles to build one, complete with answers to questions I can ask the guard, if it's not built yet) and gives my code directions to that museum.

For legibility if I'm asking the guard at the door lots of questions, I might instead write:

UtilityDefaultInfoOftenNeeded useful = UtilityDefaultInfoOftenNeeded.getInstance();
Id rtID = useful.getRecordTypeId('Admissions', 'Opportunity');
// (etc.)

Note that I don't say "new UtilityDefaultInfoOftenNeeded()." I made the "constructor" private on purpose to prevent that. Instead, I say "UtilityDefaultInfoOftenNeeded.getInstance()."

In fact, the inability to use "new ..." is really what makes it a "Singleton." You're not allowed to demand that a new "best car ever" or "default settings" museum be built. You can only ask for directions to it via a "public static" method like "getInstance()" and trust that one single museum will exist by the time "getInstance()" returns directions to the museum.


Here's how "UtilityDefaultInfoOftenNeeded" is written (as a "lazy-instantiation singleton"):

public class UtilityDefaultInfoOftenNeeded {
    
    // Please note that many values returned by the "getter" methods of this class could return null,
    // so be sure to check returned values for "== null" if that is important to your code calling these methods.
    
    private static UtilityDefaultInfoOftenNeeded instance = null;
    
    private Id defaultAccountId; // Fallback "Account" for new "Contact" records where not specified
    private Id defaultOwnerID; // Fallback record owner ID for records in the database
    private Id defaultLeadNurturerID; // The User ID of the "default" "Lead Nurturer" staff member

    private Map rtIDs = new Map(); // For holding data from the "Record Type" object


   
    // Private constructor - this is a Singleton class
    private UtilityDefaultInfoOftenNeeded() {
        // In this constructor, we do any computationally expensive or limit-worrisome computations
        // that should be done as soon as the object is instantiated (rather than when the data
        // is requested through a public object-level "getter" method).
        // Checking for "null" is not necessary because this is a constructor - all variables are null so far.
        // We will not populate every object-level variable in this constructor.
        // Some object-level variables' values are rarely needed and more expensive to compute, so we will "lazy data fetch"
        // them in their getter methods.
            if (Schema.getGlobalDescribe().keySet().contains('default_settings__c')) {
                // Set default Contact Account ID, Owner ID, and Lead Nurturer User ID
                // First, grab the "Default Settings" custom setting:
                Default_Settings__c cs = Default_Settings__c.getInstance();
                System.debug('Default Settings custom setting consists of:  ' + cs);
                // Next, initialize Default IDs from this setting:
                defaultAccountId = String.isBlank(cs.Account_ID__c) ? null : cs.Account_ID__c;
                defaultOwnerID = String.isBlank(cs.Owner_ID__c) ? null : cs.Owner_ID__c;
                defaultLeadNurturerID = String.isBlank(cs.Lead_Nurturer_User_ID__c) ? null : cs.Lead_Nurturer_User_ID__c;
            }
    }
    
    // There should be just 1 public method in this class that is STATIC:  "getInstance()."
    public static UtilityDefaultInfoOftenNeeded getInstance() {
        // Lazy instantiation
        if (instance == null) instance = new UtilityDefaultInfoOftenNeeded();
        return instance;
    }
    
    // These three methods may return null Id-typed values, so be sure to check the return value before using.
    // (Developer note - no need to "lazy-data-fetch" these values, as they "lazy-fetched" upon instantiation in the constructor.)
    public Id getDefaultAccountId() {return defaultAccountId;}
    public Id getDefaultOwnerID() {return defaultOwnerID;}
    public Id getDefaultLeadNurturerID() {return defaultLeadNurturerID;}
    
    // This method may return a null Id-typed value, so be sure to check the return value before using.
    public Id getRecordTypeId(String devName, String sObjName) {
        if (rtIDs.isEmpty()) {
            // Lazy data fetch of entire "RecordType" table into this object's "rtIDs" private variable
            for (RecordType rt : [SELECT Id, DeveloperName, SObjectType FROM RecordType]) {
                rtIDs.put((rt.DeveloperName + ';' + rt.SObjectType), rt.Id);
            }
        }
        // Grab the relevant ID and return it (or "null" if not found)
        Id idToReturn = null;
        if (rtIDs.containsKey(devName + ';' + sObjName)) {idToReturn = rtIDs.get(devName + ';' + sObjName);}
        return idToReturn;
    }
    
}

(If you see "string string" end tags at the end of this code, ignore them - my code formatter is inserting them.)


And here's its test class:

@isTest
private class UtilityDefaultInfoOftenNeededTest {

    static testMethod void testInfoOftenNeeded () {
        
        // Set up default custom settings (these are data in a table, so they don't exist in seeAllData=false test classes & need to be made in the test)
        DefaultTestDataAccountFactory.makeAndSetADefaultTestingAccount();
        DefaultTestDataOwnerFactory.makeAndSetADefaultTestingOwner();
        DefaultTestDataLeadNurturerFactory.makeAndSetADefaultTestingLeadNurturer();

        UtilityDefaultInfoOftenNeeded useful = UtilityDefaultInfoOftenNeeded.getInstance();
        
        Test.startTest();
        Test.stopTest();
        
        System.assertEquals(TRUE, useful.getDefaultAccountId() != null, 'getDefaultAccountId() is null.');
        System.assertEquals(TRUE, useful.getDefaultOwnerID() != null, 'getDefaultOwnerID() is null.');
        System.assertEquals(TRUE, useful.getDefaultLeadNurturerID() != null, 'getDefaultLeadNurturerID() is null.');
        System.assertEquals([SELECT Id, DeveloperName, SObjectType FROM RecordType WHERE DeveloperName = 'Admissions' AND SObjectType = 'Opportunity'].Id, useful.getRecordTypeId('Admissions', 'Opportunity'), 'getRecordTypeId(...) is null.');
    }
    
}

Certified Salesforce Admin

Woohoo - this blog saved my employer $200 and pushed me to get certified! I entered a "blog or do trailhead for a free cert voucher" drawing, took the test without any special studying, and guessed well enough to pass! (We don't use Chatter, Communities, Cases, etc. so there was a LOT of guessing to do.) Fortunately, at least 1/3 of the questions were data-architecture type questions that made intuitive sense rather than requiring memorization. You'll want to have done some sort of formal studying being an admin (I took ADM201 when I started this job), but it wasn't nearly as bad as I thought it'd be. The voucher was the difference between waiting until I was "ready" and taking the test "just to see what was on it" (as a woman in a developer community encouraged), so yay.