Building An Apex Portfolio
Posted: August 06, 2022

Building An Apex Portfolio

Table of Contents:

  • Frequency Counter: The Naive Approach
  • Frequency Counter: A Nuanced Approach
  • The Value Of Open Source

Open source work is an incredible way to build up a corpus of referenceable work when applying to jobs, but there are other reasons you might want to build up a portfolio of Apex-related code. Learning how to package and distribute code, for example, or giving yourself the chance to show off a piece of functionality you’re particularly proud of.

My team met up for the first time in person recently, and we were left reflecting on how it’s not always easy to get an idea for somebody’s technical acumen by way of looking at their GitHub; not everybody has an online, code-related, presence, and there’s nothing wrong with that. That being said, if you’re interested in highlighting work that you’ve done — especially if you can imagine a way to generalize a solution, making it appealing to people across a wide variety of industries — chances are that you (and others!) stand to benefit by uploading that code.

I recenly spoke with Josh Birk, from the Salesforce Developer Podcast, about the value that open source work provides (both to companies and to the individuals taking part in contributing) — this was a great follow-up to our prior conversation about The Joys Of Apex!. There were a few loose ends from that conversation that I’d like to tie up here. As well, I was meandering through Brook Johnson’s YouTube channel recently and saw him talking about the "frequency counter" pattern. I think it’s a great example of how even something very simple can end up as an object-oriented showcase. Let’s dive in.

Frequency Counter: The Naive Approach

The classic frequency counter problem presents the user with a list with various strings in it. Since we practice Test Driven Development, let’s represent the simplest possible version of a counter by means of a map:

@IsTest
private class FrequencyCounterTests {

  @IsTest
  static void returnsFrequencyCountOfEachListItem() {
    List<String> inputs = new List<String>{
      'cat',
      'dog',
      'cat',
      'dog',
      'horse',
      'cow',
      'cat'
    };

    Map<String, Integer> keyToCount = new FrequencyCounter().process(inputs);

    System.assertEquals(3, keyToCount.get('cat'));
    System.assertEquals(2, keyToCount.get('dog'));
    System.assertEquals(1, keyToCount.get('horse'));
    System.assertEquals(1, keyToCount.get('cow'));
  }
}

Bearing in mind that we don’t actually have a FrequencyCounter class, let’s go off and create one. To start, I’ll simply show the naive approach to implementing something like this:

public class FrequencyCounter {

  public Map<String, Integer> process(List<String> inputs) {
    Map<String, Integer> keyToCount = new Map<String, Integer>();
    for (Integer index = 0; index < inputs.size(); index++) {
      String input = inputs[index];
      if (keyToCount.containsKey(input)) {
        continue;
      }
      Integer count = 1;
      for (Integer innerIndex = index + 1; innerIndex < inputs.size(); innerIndex++) {
        String nextInput = inputs[innerIndex];
        if (nextInput !=  input) {
          continue;
        }
        count++;
      }
      keyToCount.put(input, count);
    }
    return keyToCount;
  }
}

Oof. Writing that hurt but our test passes. Sometimes you can’t argue with results. This is the sort of code that should be setting off alarm bells; in general, nested for loops are an anti-pattern. The technical term to look up (if you’d like) would be “Big O notation,” and the implication for developers is that for some list size (which we will examine in a moment), the solution shown above will begin to degrade severely in performance; for a large enough list size, calling FrequencyCounter.process will essentially “crash” your program (by timing out).

We can start to demonstrate the effects of this very simply:

// in FrequencyCounterTests
@IsTest
static void letsBenchmark() {
  List<String> inputs = new List<String>();
  Integer desiredNumberOfInputs = 100;
  for (Integer index = 0; index < desiredNumberOfInputs; index++) {
    inputs.add(String.valueOf(index));
  }

  new FrequencyCounter().process(inputs);
}

We can easily update the desiredNumberOfInputs variable to show how calculating the frequency by using an inner for loops is an extremely inefficient task:

# with desiredNumberOfInputs = 100
TEST NAME                            OUTCOME  MESSAGE  RUNTIME (MS)
───────────────────────────────────  ───────  ───────  ────────────
FrequencyCounterTests.letsBenchmark  Pass              67

# with desiredNumberOfInputs = 1000
TEST NAME                            OUTCOME  MESSAGE  RUNTIME (MS)
───────────────────────────────────  ───────  ───────  ────────────
FrequencyCounterTests.letsBenchmark  Pass              4042

# with desiredNumberOfInputs = 10,000 (fails after 15 seconds)
TEST NAME                            OUTCOME  MESSAGE                                                       RUNTIME (MS)
───────────────────────────────────  ───────  ────────────────────────────────────────────────────────────  ────────────
FrequencyCounterTests.letsBenchmark  Fail     System.LimitException: Apex CPU time limit exceeded
                                              Class.FrequencyCounter.process: line 11, column 1
                                              Class.FrequencyCounterTests.letsBenchmark: line 32, column 1

The good news is that since we’re already returning data with a map, we also have a much more efficient way to calculate the frequency of something:

public class FrequencyCounter {

  public Map<String, Integer> process(List<String> inputs) {
    Map<String, Integer> keyToCount = new Map<String, Integer>();
    for (String input : inputs) {
      Integer currentCount = keyToCount.containsKey(input) ?
        keyToCount.get(input) : 0;
      // we rarely see ++ used BEFORE the variable
      // because we typically are reference integers by reference
      // since "currentCount" is not a member variable of a class
      // ++currentCount correctly adds to the INCREMENTED value of
      // currentCount to the map. See "Expression Operators" in
      // the developer docs for more information!
      keyToCount.put(input, ++currentCount);
    }
    return keyToCount;
  }
}

That’s so much better, and the benchmark stats prove it:

# with desiredNumberOfInputs = 100
TEST NAME                            OUTCOME  MESSAGE  RUNTIME (MS)
───────────────────────────────────  ───────  ───────  ────────────
FrequencyCounterTests.letsBenchmark  Pass              30

# with desiredNumberOfInputs = 1000
TEST NAME                            OUTCOME  MESSAGE  RUNTIME (MS)
───────────────────────────────────  ───────  ───────  ────────────
FrequencyCounterTests.letsBenchmark  Pass              40

# with desiredNumberOfInputs = 10,000
TEST NAME                            OUTCOME  MESSAGE  RUNTIME (MS)
───────────────────────────────────  ───────  ───────  ────────────
FrequencyCounterTests.letsBenchmark  Pass              348

Even here, though, we’re still returning a map. Calling code might mutate values in the map; it might clear the map accidentally. Data access issues are typically solved through encapsulation. Let’s move on from our naive approach to something more sophisticated.

Frequency Counter: A Nuanced Approach

Let’s make a class that we can return instead of the map:

public class FrequencyCounter {

  public Counter process(List<String> inputs) {
    Counter counter = new Counter();
    for (String input : inputs) {
      /** accessing a private variable directly?? what??!
      * this is just another example of why inner classes -
      * classes declared inside another class - are awesome;
      * within the OUTER class you can access the private variables
      * without issue. But ... this breaks encapsulation.
      * for now, we're just trying to make the tests pass - we
      * will fix this the "right way" later
      **/
      Integer currentCount = counter.keyToCount.containsKey(input) ?
        counter.keyToCount.get(input) :
        0;
      counter.keyToCount.put(input, ++currentCount);
    }
    return counter;
  }

  public class Counter {
    private final Map<String, Integer> keyToCount = new Map<String, Integer>();

    public Integer getCount(String value) {
      return this.keyToCount.get(value);
    }
  }
}

With a slight update to our tests, we’re back on track:

@IsTest
private class FrequencyCounterTests {

  @IsTest
  static void returnsFrequencyCountOfEachListItem() {
    List<String> inputs = new List<String>{
      'cat',
      'dog',
      'cat',
      'dog',
      'horse',
      'cow',
      'cat'
    };

    FrequencyCounter.Counter counter = new FrequencyCounter().process(inputs);

    System.assertEquals(3, counter.getCount('cat'));
    System.assertEquals(2, counter.getCount('dog'));
    System.assertEquals(1, counter.getCount('horse'));
    System.assertEquals(1, counter.getCount('cow'));
  }

  @IsTest
  static void letsBenchmark() {
    List<String> inputs = new List<String>();
    Integer desiredNumberOfInputs = 100;
    for (Integer index = 0; index < desiredNumberOfInputs; index++) {
      inputs.add(String.valueOf(index));
    }

    // nothing needs to change since we weren't capturing the output here
    new FrequencyCounter().process(inputs);
  }
}

And now that we’ve started to encapsulate the behavior in the FrequencyCounter class, we can start doing things that are a little more interesting (using the tests, of course!):

// in FrequencyCounterTests
@IsTest
static void tracksLargestCount() {
  // if we keep using this same simple list
  // it might make sense to promote it to a private static
  // class variable accessible for any test
  List<String> inputs = new List<String>{
    'cat',
    'dog',
    'cat',
    'dog',
    'horse',
    'cow',
    'cat'
  };

  FrequencyCounter.Counter counter = new FrequencyCounter().process(inputs);

  System.assertEquals(3, counter.getLargest().size());
}

Now, we could do something boring, like iterate through the map and keep track of the max count, but that’s no fun at all. Let’s do it the fun way:

public class FrequencyCounter {

  public Counter process(List<String> inputs) {
    Counter counter = new Counter();
    // ... we're still accessing the "private" counter
    // variables here, but we're ALMOST ready to refactor that
    return counter;
  }

  public class Counter {
    private final Map<String, Integer> keyToCount = new Map<String, Integer>();

    public Integer getCount(String value) {
      return this.keyToCount.get(value);
    }

    public SentinelValue getLargest() {
      List<Integer> sortedValues = new List<Integer>(this.keyToCount.values());
      sortedValues.sort();
      Integer largest = sortedValues.get(sortedValues.size() - 1);
      for (String key : this.keyToCount.keySet()) {
        if (this.keyToCount.get(key) == largest) {
          return new SentinelValue(key, largest);
        }
      }
      return null;
    }
  }

  public class SentinelValue {
    private final String key;
    private final Integer size;
    public SentinelValue(String key, Integer size) {
      this.key = key;
      this.size = size;
    }

    public Integer size() {
      return this.size;
    }

    public String value() {
      return this.key;
    }
  }
}

Sadly, the “fun” way (with sorting the value list for the map) doesn’t really hold up that well in its current iteration; we don’t want to be returning a new SentinelValue instance every time somebody calls getLargest(). One way around this would be to implement lazy-loading:

// in FrequncyCounter
public class Counter {
  private final Map<String, Integer> keyToCount = new Map<String, Integer>();
  private SentinelValue largestValue;

  public Integer getCount(String value) {
    return this.keyToCount.get(value);
  }

  public SentinelValue getLargest() {
    if (this.largestValue == null) {
      List<Integer> sortedValues = new List<Integer>(this.keyToCount.values());
      sortedValues.sort();
      Integer largest = sortedValues.get(sortedValues.size() - 1);
      for (String key : this.keyToCount.keySet()) {
        if (this.keyToCount.get(key) == largest) {
          this.largestValue = new SentinelValue(key, largest);
          break;
        }
      }
    }
    return this.largestValue;
  }
}

That’s not half-bad. Still, we can benefit from a more traditional approach. Lazy-loading tends to add lines of code since each property that we want to lazy-load needs if statement guard clauses to check for null. We don’t yet have the “call to action” to refactor this “problem;” nor have we been able to tackle the issue with FrequencyCounter manipulating its Counter class’s internal state …

A funny thing happens when we go to write a naive implementation for the following test, though:

// in FrequencyCounterTests
@IsTest
static void tracksSmallestCount() {
  List<String> inputs = new List<String>{
    'cat',
    'dog',
    'cat',
    'dog',
    'horse', // 1
    'cow', // uh oh, this is also 1
    'cat'
  };

  FrequencyCounter.Counter counter = new FrequencyCounter().process(inputs);

  // so far, so good
  System.assertEquals(1, counter.getSmallest().size());
  // .... hmmm ....
  System.assertEquals('horse', counter.getSmallest().value());
}

You’ll note that both “horse” and “cow” are tied for the smallest frequency; we’ve now (unwittingly) introduced a significantly different architectural challenge into the mix. I recently had somebody imply that over-architecting code when there’s no need for it is an ever-present danger when introducing architectural considerations into the coding mix. I tend to find test-driven development the nice counterpoint to that assertion: in writing that test, I’ve specifically created a scenario where tie breakers need to be considered, and addressed. And while it’s relatively trivial to track the largest value while iterating through a list, it’s decidedly not trivial to have to track the smallest value, too. Think about it:

  • we can’t simply decrement the currentCount variable because we don’t know for sure how far back in the count we need to go
  • we aren’t tracking the count per key anywhere, so even if we did know how far back to go, we also need to tie each numbered count to the keys it’s associated with

Again — by merely writing this test, I’ve actually introduced the need for additional architecture, because this is what I really want to write on the last line of that test:

System.assertEquals(new List<String>{ 'horse', 'cow' }, counter.getSmallest().values());

Of course, these key points are part of my ultimate goal in expanding upon the “Frequency Counter” concept to begin with:

  • showing how even relatively simple concepts tend to expand in responsibility over time
  • how tests can help “drive out” architecture by delaying unnecessary pieces of code until they’re needed
  • how even the most abstract examples tend to have concrete lessons that can be applied to any codebase
  • the value in being able to display your ability to iterate within an online portfolio (more in this once we finish the example)

So let’s do this the idiomatic way, by updating the SentinelValue API (from value() to values()), and dealing with the cascading changes that causes:

public class FrequencyCounter {

  public Counter process(List<String> inputs) {
    Counter counter = new Counter();

    for (String input : inputs) {
      counter.trackFrequency(input);
    }

    counter.finishSetup();
    return counter;
  }

  public class Counter {
    private final Map<String, Integer> keyToCount = new Map<String, Integer>();
    private final Map<Integer, Set<String>> countToKeys = new Map<Integer, Set<String>>();
    private SentinelValue largestValue { get; private set; }
    private SentinelValue smallestValue { get; private set; }

    public Integer getCount(String value) {
      return this.keyToCount.get(value);
    }

    public SentinelValue getLargest() {
      return this.largestValue;
    }

    public SentinelValue getSmallest() {
      return this.smallestValue;
    }

    private void trackFrequency(String input) {
      Integer currentCount =  0;
      if (this.keyToCount.containsKey(input)) {
        currentCount = this.keyToCount.get(input);
        this.countToKeys.get(currentCount).remove(input);
      }
      currentCount++;
      Set<String> currentKeys = this.countToKeys.containsKey(currentCount) ?
        this.countToKeys.get(currentCount) :
        new Set<String>();
      currentKeys.add(input);
      this.countToKeys.put(currentCount, currentKeys);
      this.keyToCount.put(input, currentCount);
    }

    private void finishSetup() {
      List<Integer> orderedKeys = new List<Integer>(this.countToKeys.keySet());
      orderedKeys.sort();
      Integer largestCount = orderedKeys.get(orderedKeys.size() - 1);
      Integer smallestCount = orderedKeys.get(0);
      this.largestValue = new SentinelValue(this.countToKeys.get(largestCount), largestCount);
      this.smallestValue = new SentinelValue(this.countToKeys.get(smallestCount), smallestCount);
    }
  }

  public class SentinelValue {
    private final List<String> values;
    private final Integer size;

    public SentinelValue(Set<String> values, Integer size) {
      this.values = new List<String>(values);
      this.size = size;
    }

    public Integer size() {
      return this.size;
    }

    public List<String> values() {
      // always return a new list so that callers can't modify
      // this class's internal state!
      return new List<String>(this.values);
    }
  }
}

And there you have it. We’ve now optimized how our SentinelValues are being initialized. We’ve iterated on our original concept and ended up with something that’s reuable, has uses well beyond the original naive Frequency Counter implementation, and — by virtue of having shown our work from start to finish — we’ve created the equivalent of a digital breadcrumb trail. The tests are all very, very happy:

TEST NAME                                                  OUTCOME  MESSAGE  RUNTIME (MS)
─────────────────────────────────────────────────────────  ───────  ───────  ────────────
FrequencyCounterTests.letsBenchmark                        Pass              31
FrequencyCounterTests.returnsFrequencyCountOfEachListItem  Pass              5
FrequencyCounterTests.tracksLargestCount                   Pass              5
FrequencyCounterTests.tracksSmallestCount                  Pass              5

Finally, we’ve handled the “tie-breaker” logic, and used that as an opportunity to encapsulate logic such that:

  • FrequencyCounter only knows about the Counter, and doesn’t touch the counter’s internals
  • Counter is responsible for tracking its own state, and for setting up the SentinelValue objects efficiently, as well

The Value Of Open Source

If we took each excerpt from the Frequency Counter example here as a separate git commit (and it’s always a great practice to keep your commits small, isolated), our git commit history might look like (ordered from first to last):

  • initial commit with naive frequency counter implementation and tests. still need to do some benchmarking.
  • added benchmarking test. need to optimize the underlying code!
  • introduced FrequencyCounter.Counter inner class to optimize implementation
  • started tracking largest value (without tie-breaking logic) passed to FrequencyCounter
  • added tracking for smallest value(s), optimized how these sentinel values are tracked

I cannot stress this enough: writing good commit messages is important. Use commit messages to communicate intent. Keep them short, and to the point. Editing your git commit history — and the messages contained therein — could be another whole post, but for now, it suffices to say that small commits (read more by looking for “atomic commits”) help you to tell a story about the work that you’ve done.

This, ultimately, is one of the takeaway messages from this post: you don’t have to have an enormous open source project that you’re actively maintaining. You don’t have to code on the weekends to build up a portfolio. All it takes is a little bit, here and there, and that little bit adds up over time. Depending on your interest level in upleveling (for more, see Uplevel Your Engineering Skills), taking part in open source software might merely mean reading. This is one of the things that I didn’t get to while talking to Josh on the podcast, but it’s something that I’ve mentioned on this blog repetitively: good developers write code. Great developers read code. The fact that reading and writing code are two separate skillsets could not be more obvious. We — as an industry of software engineers — have a massive problem when it comes to code reviews and understanding the intent of other developers. The best way to start upleveling your reading skill — if you work on a team — is to read your team’s code. Not everybody works on a team; not everybody will get the chance to learn as much as they might elsewhere if the pre-existing code isn’t well-maintained. That’s perfectly alright!

The next best way to uplevel your code-reading skills is through open source (see A Year In Open Source for another short reflection piece on valuable lessons learned). There are dozens (if not hundreds) of great Salesforce-related repositories on GitHub. Being able to increase the speed by which you can scan, read, understand how code works, and integrate those learnings into your own work is an enormous point in your favor; being able to point to publicly accessible repos while talking about code patterns you like, mistakes you’ve learned from, etc … will always help prove your competence.

For example, let’s say you’re reading a repository:

  • you note the author’s made a class called InfinityCycler
  • you go into the class and read it
  • ahhh — it’s a recursive queueable template
  • you wonder why the author didn’t name the class something more easily understood

You might remember from Naming Matters in Apex that I don’t believe in “cute” or “clever” class names because they fail to meaningfully signify intent. Being able to speak to that (and why it’s important)? That’s another point in your favor.

Another thing that I wasn’t able to fit into the most recent podcast conversation I had with Josh is more specifically relevant for those looking to contribute to open source repositories, and that’s what a tricky balance it can be between our programming ideals and the reality of a situation for something like a packaged solution. I would love to take my own work on the Repository pattern and use that within Apex Rollup. I’ve settled for using a much simpler dependency injection framework for that project specifically because I don’t want to increase the package size more than is necessary. Reading “project”-sized codebases is a great chance to practice your critical thinking skills:

  • why did the author(s) choose to do things a certain way?

    • do you agree with their methods?
    • if you disagree, why?
  • are there any obvious “gotchas” or things that you’ve learned that the author(s) aren’t taking advantage of / might not be aware of?

    • if you spot a problem or want to contribute — do they have a Contributing guideline (on GitHub, it’s one of the recommended files every repo should have) that you can use to understand if it would be more appropriate to file an issue or fork the repo and try to create a Pull Request?
  • are there areas for improvement, or lessons you can takeaway from having read the codebase?

    • for example, back when Gatsby.js was open source, I focused on submitting improvements for its image generation plugin because at the time, improving the accessibility and efficiency by which images were created was a focus of mine
    • does the project use some kind of custom style guide? Would the author(s) be amenable to conforming to an auto-formatting solution? This is low-hanging fruit for any repository that doesn’t use code formatting; formatting code by hand is an enormous waste of time, and having a custom style guide for a repository — or using an undocumented code style that is then nitpicked on potential contributions — is just another form of gatekeeping

Again, these are things that can uplevel your engineering skill even if you choose to never contribute. I remember telling a friend of mine after having contributed to another repository about the frightening amount of committed whitespace, odd line breaks, and other formatting irregularities. I still ended up contributing to that repository, but forewent some of the larger architectural efficiencies I’d hoped to introduce once I saw how bizarrely formatted the repository was. If nothing else, it makes for a good story.

Lastly — open source is a really interesting avenue for giving back to the community. I know that I’ve benefitted tremendously from the knowledge shared with me by industry veterans, and creating & contributing to open source repositories helps make the community better for everyone. That’s a really powerful thing — the act of paying it forward — and the altruism that shows is all the more appreciated. At the end of the day, most of us need to make money in order to survive; I have nothing against hustle culture, and the entrepeneurship I see from many people, but I think there’s also something incredibly powerful about being able to give away your labor instead of monetizing something valuable. One need only look at the benefits people derive from having access to public parks, protected spaces, free museums, etc … to understand what I’m talking about here.

As always — thanks for reading. Thanks for making it this far, too! I’m a positive person by nature, but I’m still always grateful to receive messages from around the world from people who have enjoyed the Joys Of Apex. I’m completely blown away that something that started so small has grown so much in just two and a half years. I’ve expanded a bit on this post, now, in Building Better Teams.

Till next time!

In the past three years, hundreds of thousands of you have come to read & enjoy the Joys Of Apex. Over that time period, I've remained staunchly opposed to advertising on the site, but I've made a Patreon account in the event that you'd like to show your support there. Know that the content here will always remain free. Thanks again for reading — see you next time!