Lessons Learned

Hello, Again

I haven’t written in a long long time. Isn’t that always the story, though. Unless you have a habit, an identity, or a following, it’s really hard to make a site like this, a blog really, worth it. And I haven’t got any of those things. I don’t know who I’m writing for, and I don’t know why I’m writing. But I’m paying for this site anyway, so I guess I might as well write something.

It’s been a year – or two. Back in August 2021, I changed jobs. I was really happy where I was at. I had been working at HireVue. I’d been a data engineer there, and a staff software engineer. I had (I think) respect, and I was doing a killer job with work that I loved to do.

Then, in – oh, I don’t remember – I think March, maybe April, of that year, Facebook knocked on my door. I’d just had a coworker leave to go to Facebook, and it felt like everyone around me was leaving, and I thought, “well, why not give it a shot?”

So, over the course of two or so months, I interviewed, prepped, fretted, all the things you do for a FAANG job, and lo and behold, I landed a job! I was on vacation when I found out. The pay was, well, amazing. I mean, it wasn’t as good as I’d been told during interviews, but it was still really really good. And the offer included relocation, which was a boon for me and my family at the time, because we’d already been looking to move.

Together with my family, we packed up, moved quick, and by August, we were living in Snohomish, Washington, and I was working – largely remotely – for Facebook, soon to be Meta.

I was a data engineer. I worked in the Infrastructure organization. And the job was nothing like I expected. All (well, most of) my interviews had been really technical. But I spent a ton of time in meetings, talking, managing projects, and just trying to figure out what the heck needed be done. I didn’t love it.

As a technologist, I love to get my hands dirty. I love to be wrapped in code. Give me a really tough problem, let me go to work, leave me alone, and I’ll come back in a day or two, or maybe a week if the problem is truly challenging, with some awesome code, a really great solution, and I’ll feel energized, I’ll love it. But stick me in a room, just listening to people talk; make me fight and work and manage and toil to do everything to get people to listen and to move a project forward. Do that, and I’m going to be exhausted and demoralized.

It’s not that I can’t do it. In fact, I do it really well, and that’s one of the things that creates problems for me. It’s hard to find people who are great technically, but who can also convey complex ideas to business folk, and who have experience managing projects and programs. I’m one of ’em. And so, inevitably, I get pulled into that kind of work because employers need people who can do it. But I hate it. I just wanna code. I love being in the tech.

Meta just wasn’t that for me. I tried. I really did. But after a year, nothing was changing, and I was unhappy. And so, I put my ear to the ground, and listened for opportunities outside of Meta. An old friend from my HireVue days came calling with an opportunity at his current company, eVisit. I knew a few folks that jumped ship from HireVue to eVisit, and I thought I’d interview.

Interviews went well, and they really wanted me, but I balked at first. I was still trying to make a go of it at Meta. And I had another potential offer at a non-profit that I was really interested in. So, I turned down the initial offer and moved on.

But after a few more months of fighting to make Meta work (fruitlessly), I decided I needed to leave for my own, as well as my family’s, health and sanity. Coincidentally, eVisit reached back out to see if I’d reconsider. I was still trepidatious. It’s hard going from a multi-billion, big-name, well-established tech company to a small start-up, but I thought, why not? Besides, I’d be able to get my hands dirty with tech again. So, I said yes.

I’ve been at eVisit for two months now. I’m the sole data engineer. There’s a ton of work to do, and a lot of opportunity to transform the data environment. As much as my time at Meta drained me, I still learned and experienced a lot, and I saw what possibilities are out there with respect to data. Even though Meta’s data environment is extremely proprietary, there were lessons to learn, and I hope I can apply at least some of what I learned from Meta to my work at eVisit.

We’ll see. The company is small. They’ve had some success, and I hope they’ll have more in the future. Creating a truly data-driven culture and implementing the tech behind it doesn’t happen overnight. I hope I have the chance to make it a reality.

Maybe, just maybe, I’ll keep this site posted with my progress.

Lessons Learned

Mornings

It’s a still morning. I didn’t want to awake, but the dogs must be let out or there will be a price to pay. It’s gray here in the living room with its northerly windows that let in only ambient light. And other than the dogs, who’ve returned from their morning ablutions in the backyard to curl up for their morning naps, I am the only one awake.

Two days ago, I got my first dose of the COVID vaccine. We made a day of it, my wife, my sons, and I. I have told several people, I cannot remember ever being so excited to get a vaccination before. But I was, I am. I am so grateful to live in a time when we understand immunology and microbiology well enough to produce vaccines. Can you imagine what a plague COVID would have been even two centuries ago?

I don’t have much to say this morning, really. I just thought it had been a few weeks and I awoke not really looking forward to the day, so some quiet reflection might do me good. And it has.

It’s good to think of the good things, even the small ones, on morning like this. Yes, things are a bit of a chaotic mess at work right now. Yes, there is bad news out there. But, I got my shot. And the morning sun has arisen far enough over the horizon that its golden beams can reach out to the red blossoms and green buds of the trees behind our house, blending into a growing white light that fills the living room.

Lessons Learned

Journaling

It all began with bullet journaling. Toward the beginning of this year, I decided to start (or restart) bullet journaling. I have a coworker who I’d seen use the technique before, so I asked him about it. Over the course of a few conversations, I ended up participating in a 30 day program to help envision my future self and make a concrete plan to get there. That program gave me daily journal prompts. I’ve spent the last two months keeping a daily journal because of it.

I had a little notebook I bought a year ago, and I made it my bullet journal. Aside from tracking daily tasks, I used it for the daily journal prompts. I also used it to collect other useful information: birthdays, definitions, and a table of companies whose financial reports I wanted to look at.

And, as February ended and March approached, I’d nearly filled the notebook. I was surprised it had gone so quickly.

I’ve found it very useful to keep that daily journal. Now that the 30 day program has ended, I have to come up with my own prompts. Usually, though, there’s something on my mind in the mornings. That becomes the basis for that day’s prompt.

The journaling helps me reflect. It helps me envision. And it helps me plan. It gives me a chance to put thoughts down. And knowing that nobody but I will read them helps me be more open in my reflection.

Anyway, I don’t have a ton more to say here. It’s been a long week, and a typical one. I just want to express my gratitude for journaling and recommend it.

finance, investing, Lessons Learned

Early Steps Toward Financial Freedom

I want to be financially free someday. And to do that, I need to figure out how to get my money working for me. I can’t constantly fritter it away and it can’t sit idly in a vault. It needs to go out into the world, work, and bring more money back.

So, I’ve embarked on a self-educating adventure. I want to find the places where I can start putting money to work. I’m conservative by nature, and I haven’t got a lot of money to begin with, so I’m probably not going to invest in some brand new business venture. But, investing in stocks seems like a simple, relatively safe, first step.

And I mean investing. I don’t have any interest in trading and trying to make my gain off market forces of supply and demand. I want to find a few decent, durable, reliable companies and buy a piece or two of them. I want to see them grow, and pay me in dividends and in the growing value of my stock in them.

To that end, I’ve spent the last week or two looking at the financial statements of a different company each day. Mostly, this is training exercise for me. I’m learning where to find the statements, how to evaluate them, how to compare two companies’ different statement, and the variety of ways companies structure their finances.

I suppose this could be boring work for some, but I find it fascinating. I’ve focused mostly on companies I interact with regularly: Apple, Microsoft, Amazon, Google, Facebook, Coca-Cola, Twitter, and others. I look at their most recent statements, either their quarterly or annual. And so far, I’ve focused on their Income statements and Balance Sheets.

I’ve found that I can easily get these statements directly from the SEC via a system called EDGAR. These statements are awesome and come in three basic forms. The most detailed is an iXBR format (I think they call it) that presents the data as an elaborate web page with hyperlinks to define numbers and filtering to get at exactly the piece of data you seek.

The EDGAR iXBR formatted income statement gives useful information about every piece of data and makes it easy to find a specific data point.

It’s almost too much information, but it’s useful when I need to find something specific or understand what I’m looking at. In contrast, the Interactive Data format is a more concise presentation of the data. The lack of clutter makes it easy to see the numbers.

The Interactive Data format presents data in a clean and concise form that makes it easy to see the whole picture

But my favorite format is XLSX. Right up there above the sidebar on the Interactive Data format, is a link to “View Excel Document.” That gets me all of the same data in a workbook where I can format it, clean it up, and calculate from it to my heart’s content.

This gives me the power to compare two different businesses. I start by going through the Income Statement and the Balance Sheet, defining names on specific cells. Did you know you can do this? A lot of people don’t because it’s something of a hidden feature.

Right-click a selection in Excel to access Define Name

After selecting one or more cells in a workbook, you can right-click and select “Define Name” from the pop-up menu. You can also find this as a button on the Formulas ribbon. Using this feature, you can give a specific name to the region of selected cells and refer to it anywhere you’d like to use it in calculations throughout your workbook. So, instead of writing a calculation to define “Current Ratio” as “=$B$7/$B$20”, you can write something more meaningful, like “=Current_Assets/Current_Liabilities”.

After naming several key cells, I then start breaking down the sheets a bit. In the Income Statement, my goal is to understand how efficiently the company operates. How much of their revenue do they retain? And where do they spend their money? On the Balance Sheet, my goal is to understand how the company structures its finances. Do they keep money on hand? Do they have a lot of property? Do they rely more on debt or on equity?

To compare different companies, I express the numbers as percentages. On the Balance Sheet, for example, next to each value, I calculate that value as a percentage of its relevant section. Say, Accounts Receivable, an asset, is in B4. In B5, I’ll enter the calculation “=(B4/Total_Assets)*100”. I divide all of the values in the assets section by “Total Assets” and everything in the liabilities and equity section by “Total Liabilities and Shareholders’ Equity”.

A Balance Sheet in Excel after calculating values as percentages and reformatting

Finally, I go through and do some formatting to make it easier to parse the data. I remove trailing decimals, I bold various totals, and I increase the font size on major sections’ totals. The end result is a workbook that I can compare to another company’s workbook so that I can understand how they equate to one another. I can see where one company has 80% of its business finances on liabilities while another company has only 20%. I can see where one company spend 50% of its income on R&D while another only spends 5%. All of this, hopefully, helps me know which of the two is in a better financial and operating position, and is, therefore, a safer company to invest in.

Really, this is all a learning exercise. Fidelity, my brokerage, does a lot of this for me. I can just look at the data they’ve compiled. But at this stage, I find it useful to do it myself. It helps me understand where the numbers come from and how they work. It also gives me some freedom to explore beyond what my brokerage provides.

And it’s been fascinating to see how different companies work. Even when I don’t know all that there is to know about a company, I have a much better sense, after looking at the finances, of which company is a safer investment. I have a better sense of the company’s longevity. And I understand better how it operates.

In the weeks ahead, I hope to use this information to give me the confidence to make some first, serious forays into the stock market. And in the years ahead, I hope to build on this foundation, perhaps to invest in developing a business that can deliver greater returns. All told, I believe financial freedom is possible with the right work.

Lessons Learned, management, positive

Forcing Functions

I’m glad I committed to write weekly on this blog. During the week, I don’t think about it much, if at all. I might ask myself whether some experience or thought I’ve had is worth blogging about, but that’s the extent of it. When the weekend comes, though, the knowledge of my commitment drives me to write something, anything to keep the commitment.

This is a “forcing function.” It’s a constraint I’ve put in place that compels me to act in a certain way. I’ve also committed to being positive on this blog. That forces me to filter my activities and thoughts during the week through a lens of positivity. This is useful because it’s easy to fall into a pattern of negativity when I’m surrounded by it in the news and on the Internet.

In journaling this morning, I thought more about forcing functions. What other forcing functions could I introduce in my life? I don’t think they need to be huge. I’ve noticed, for example, that there are things I avoid doing at work because they would be forcing functions that might drive me in ways I don’t want to go, or steal away some of my “freedom.”

For example, at work, I try to avoid saying what I’m going to do. Instead, I do it, and report it later. This is a way to avoid committing to something I’ll later be held accountable to deliver. It seemed like a good idea when I developed this habit, and it may have merit in some situations. But I think it has downsides, too.

Stating my intent to do something is like making a little promise. And because I value my status amongst my peers, that little promise becomes a forcing function that drives me to action. Add to that a deadline and it will drive me to deliver faster. It doesn’t have to be a big bold statement. A simple statement like, “Let me take a look at that and get back to you later today,” is enough to do the trick.

This is a habit that can be tremendously useful in helping me achieve my goals. I don’t need to state my intent about everything I’m planning to do. If I save those statements for actions that will push me in the direction of goals, then they will have the advantage of compelling me to move in the direction I want to go.

That’ll be a focus for me in the weeks ahead at work, and maybe even outside work, too. Making this statement here is a perfect example. Having made it, I’ve made a commitment, and I already feel compelled to act on it. Here’s hoping it pays off…

Lessons Learned, management

Finding the Right Job for the Candidate

Somewhere in Hollywood is a building with pale green carpets and plain white walls. I sat in that room with a dozen other boys like me, child actors, committing my lines to memory and building my emotions into a rage. When my time came, I stood in front of the casting director, the director, and a camera, and I read my lines with such fury and anger that they had to ask me to scale it back. It was a great interview.

A few days later, the call came. They’d loved me. The interview had been great. They wanted me for the part. But I was too tall.

That was interviewing. There was always something. Especially during those awkward teenage years when I was growing so fast that my photos couldn’t keep pace with reality. I was too big for the kid parts and too young for the high school parts. I was too this, not enough that. I got the part, they changed the concept, I lost the part. I can’t tell you how many times I was this close to working with this famous director and that famous actor for a movie at that major studio.

What I learned about interviews was that they were about judging and being judged. They were about a hundred kids and their mothers packed in a small room working their way through a process that would whittle the herd down to one lucky kid who might get the role that would make them a star. And when I got to be an adult who interviewed for “real-world” jobs, those interviews felt much the same.

Over the course of my career, I’ve been on the other side of the table many times. I’ve been the interviewer instead of the interviewee. I can’t recall ever having been trained to that role. It always seemed like the occasional job responsibility. Program this, help the customer with that, and, oh yeah, judge this person so we can decide whether they get to work or not.

I guess, what I’m saying is that it has always felt kind of cold and heartless. I know it shouldn’t be, but nobody really trains you how to do it better. What training does exist almost seems like it’s tailored to drive home the point that your job is to be a passionless interrogator intent on getting at the truth that will help you know, without a doubt, that you’ve hired the right candidate for the job.

This week, I sat in an interview again. But it was different this time. Maybe it’s years, now, of practice. Maybe it’s my different perspective, my aim to help others and to mentor. I don’t know. But as I sat there, I felt less as though it was my job to judge the fit of the candidate to the job at hand, and more my responsibility to fit the job to the candidate.

It’s a subtle difference in perspective, but I think it’s important. Candidates are good people who want to work. They’ve gone to school, they’ve compiled a resume of past experiences, they’ve dressed up and practiced, all to impress me because they hope I will give them a chance. They want a chance to work, to grow, to make some money to put food on their table. The problem of an interviewer is seldom that the candidate is unwilling or unable to do the job.

The real problem is finding a candidate who will become an employee who stays and who continues to grow. That requires a candidate who is motivated by what I, and my company, bring to the table. Part of that is the basic stuff – the salary, the benefits, the workplace. Most of it, though, is the job itself. Is it work that is intrinsically interesting to the person on the other side of the table?

As I prepared for and participated in the interview this week, this was what on my mind. I asked questions aimed at understanding this person’s aspirations and interests. What did this person want to be known for, what did they want their coworkers to say about them? “Go see ___. They are great at x!” What is x? Conversely, what aspect of themself does the candidate want to work on that they feel they need to improve?

And then, I looked at the role at hand and asked myself whether it was a role that would help them achieve these aspirations. Was it a role where this person could really get good at x? Would this role be one that aligned well with their desire to improve in this other area?

As an interviewer, I have a chance to make someone’s life better. Obviously, it’s better when they are working, and I would love to give a job to everyone who wants one. Barring my ability to do that, though, I can help improve lives by helping candidates find a job that really speaks to them. I’m hiring a candidate not just for this job, but for the next one, too, the one that this job will prepare them for.

I hope that the candidate will stay, of course. I hope they’ll thrive in this job. I hope that they will grow in the job and feel invested in it. All of that, though, is far more likely to happen if this is the right job for them. If this is the right job for them, they will want to stay, they will want to grow, they will work at it because it interests them, and they will find joy in the job.

Interviewing is a chance to help others. It’s a chance to help people find the job that will help them become what they want to be. And that’s something I care about.

Lessons Learned, positive

Positivity

Yesterday, I wrote a post about meme stocks (a post I’ve since deleted). That was my post for the week. Yesterday was Saturday, and I had a really good day. Early in the day, I posted to Facebook about my aspirations for my future and I received a lot of positive and encouraging feedback. As I went to bed in the evening and thought back on the day, I realized that the one blemish on my otherwise positive day was my own post.

And so, I decided to delete it. It was negative and it came from a place of complaint. It was preachy and sought to place blame on a single group for something truly complex. It may have been right. It may have been wrong. Regardless, it’s just not what I want to produce and add to the world.

There is enough negativity. Last year was filled with it. And I don’t need to contribute to it. That doesn’t mean I don’t experience bad things in life. And it doesn’t mean I’m universally positive. In fact, positivity isn’t my default state. But I’m trying to change. I’m trying to help people. And I think I can do more good by being positive than I can by being negative.

In fact, that’s something I’ve seen over the last couple of days. Yesterday, my positive post on Facebook attracted more response than any post I’ve had in recent memory. Many of the respondents were people I haven’t heard from in years. Positivity attracts people, and it attracts more positivity.

Yes, there is enough negativity in the world already. Life is filled with hardships. Positivity won’t necessarily get rid of all of those hardships, but it won’t add to them, and it will help alleviate some of them. I would much rather help people overcome hardships than add to them.

We have an opportunity to help each other in life. We all will struggle from time to time. How great would it be to have friends who encourage us, lift us, and help us through those struggles? That’s the kind of positivity I want to bring to life. And it’s the kind of positivity I want here.

Lessons Learned

Starting Anew

Is this experience familiar? You decide you need to start keeping a journal again. You open up your journal and begin writing. “It’s been too long since I last wrote,” you begin. You continue with a quick catch up of where you are in life. You comment on your desire to improve. And you commit to keeping a journal regularly.

As you finish, you leaf back through the pages to see when you last wrote. It was more than a year ago. Here’s what you said, “It’s been too long since I last wrote…,” And you closed that entry with a promise to keep a regular journal.

Journaling is a habit, and – like any habit – it takes concentrated effort to form at first. It’s a habit that I don’t have. Though, it’s a habit I’d like to establish.

I started this blog several years ago to fill a requirement in a business class. Each week, I was given a prompt to address here, in this blog. I’ve tried to bury those early posts because they no longer fully reflect who I am or what my priorities are. This being the Internet, I’m sure they’re recoverable.

As the class that prompted this blog came to its end, so did my regular upkeep of it. That’s a shame. I pay for the domain. And I think there’s some value to having a “vanity” domain to stamp something of my own mark on the Internet.

In the intervening years, I’ve tried to write here now and then. I’ve tried to establish some identity for this blog. But my efforts have been spurious at best.

The challenge, I think, is that I’ve always tried to scope the blog to some particular purpose or identity. And when I do that, I recognize two things. First, nobody really knows this blog exists. It’s public, so of course people can find it, but they’re not gonna find it unless they’re looking for it. Second, given my inconsistency, virtually any subject I might write on is almost certainly covered more thoroughly and reliably elsewhere.

But I need to create. I need a place to put my thoughts down. I want to have some identity beyond the circle of my close friends and associates. And so, against my own doubt that it will actually go anywhere, I will give this all another shot. And here’s what I plan to do.

Rather than try to tailor my site and my writing to one particular purpose, I’m going to make this more of a traditional blog or journal. In nearly every week of my life, there is at least one day, one hour, or one moment when I have a thought or opinion or experience that I think I’d like to share with others. That’s what I’ll do here. I will strive to write something every week, reflecting on my week, and capturing that opinion or experience I want to share.

Could I write more frequently? Sure. And maybe I will. But my only commitment at this point is to write once a week. Will my thoughts coalesce around a particular topic? Maybe. But that’s not my goal. My goal is simply consistency.

My hope is that, as I write consistently, there will be certain topics or ideas that become natural focuses for my writing. When, and if, there are, then I may spin those off into their own separate sections for this site, or I may redefine this site in terms of those topics. For now, though, consistent writing about my weekly thoughts and experiences is what I am committed to deliver.

Here goes nothing…

Lessons Learned

My Philosophy: EAFP vs. LBYL in Python

In Python, there are two philosophies when it comes to accessing items in a dictionary. The first is EAFP (“Easier to Ask Forgiveness than Permission”), which says to access the key and handle a KeyError if one arises. The other is LBYL (“Look Before You Leap”), which says to check for a key’s existence in the dictionary before trying to access it. As with many programming philosophies, different people have different opinions about which approach to use.

This post on StackOverflow that first brought these philosophies to my attention pointed me to the Python glossary entry on EAFP. The Python glossary seems to prefer EAFP in its definition of the term.

In the past, though, I’ve tended to prefer LBYL. The author of Effective Python: 90 Specific Ways to Write Better Python seems also to lean that way in his recommendation, “Prefer get Over in and KeyError to Handle Missing Dictionary Keys.”

I decided on a compromise between the approaches based on a sidebar at RealPython.

My philosophy is to use EAFP when we expect the key to be in the dictionary in most cases but can handle it if the key is absent, and to use LBYL when we don’t expect the key to be in the dictionary in most cases but want to do something if it is present. A recent situation illustrates the philosophy in action.

I have a function that accepts a dictionary as a parameter and uses it to perform some action, e.g.:

def my_function(my_data: Mapping[str, Any]) -> None:
    write_to_table(my_data['id'], my_data['name'])

This is an extremely simplified example of a function that uses the dictionary, my_data, to provide arguments to another function.

I want to add some optional data to the dictionary. When present, the function should take additional action based on that data. Most of the time, though, this optional data will not be in the dictionary. This is where I would use the LBYL approach because:

  • The key is optional
  • The normal case is for the key to be absent

So, I end up with the following:

def my_function(my_data: Mapping[str, Any]) -> None:
    write_to_table(my_data['id'], my_data['name'])
    other_data = my_data.get('other')
    if other_data:
        write_to_other_table(other_data)

I have another function that gets some data from a database and cleans it up a little before returning it to the caller, e.g.:

def read_from_table(my_id: int) -> Mapping[str, Any]:
    my_data = get_from_db(my_id)
    try:
        del my_data['id']
    except:
        pass
    return my_data

In this function, there is a key I want to delete from the dictionary. In this case, I expect the key to be present most (actually, all) of the time, but I can keep going even if the key is absent (since I want to delete the key anyway). I use the EAFP approach here because:

  • The key is optional
  • The normal case is for the key to be present

In summary, when it comes to choosing between EAFP or LBYL, my philosophy is to use the approach that best expresses my expectation for what is the normal case, i.e. what I expect to be true most often:

  • Use EAFP when you expect the key to be present most of the time
  • Use LBYL when you expect the key to be absent most of the time

I believe this makes it explicit to a reader what I expect the usual case to be, and being explicit in my intent is Pythonic.

Lessons Learned

Error-handling in Go

One of the tiresome aspects of Go is its standard idiom for error-handling. In that idiom, functions return errors, callers check those errors, and then take action accordingly. Typically, that action is for the caller to return the error to its own caller, gradually passing it up the call-stack for something else to handle. In practice, this results in a lot of boilerplate code that looks like this:

func MyFunc() (int, error) {
  n, err := someOtherFunc()
  if err != nil {
    return nil, err
  }

  if err := anotherFunc(n); if err != nil {
    return nil, err
  }
 
  return n, nil
}

The code above illustrates a couple of variations on the technique, but the pattern should be evident. Call after call, you check the err to see if it’s nil.

This has two implications. First, there’s a lot of redundancy; it can quickly become tedious typing the same code again and again. Second, it gets hard to see what’s going on with the code because it’s hidden behind all of this error handling code. I’ve found there are a couple of things I can do.

If I strive for functional cohesion, I tend to end up with functions that have fewer lines of code because they focus on specific tasks. As a result, I end up having to write less of this boilerplate code per function simply because there’s less code per function generally.

For more complex functions, such as those with procedural cohesion, I can often use some sort of centralized error to make the procedure in the function more apparent by hiding the error-handling. Let me provide a couple of examples of this.

Central Error Handling in a Builder

In a current project, I have a Builder type similar to the GoF builder pattern. Something like this:

type Builder struct{
...
}

func (b Builder) AddObjA(a ObjA) error {
...
}

func (b Builder) AddObjB(b ObjB) error {
...
}

func (b Builder) constructPartA() (Product, error) {
...
}

func (b Builder) constructPartB(p Product) (Product, error) {
...
}

func (b Builder) finishConstruction(p Product) (Product, error) {
...
}

func (b Builder) GetFinishedProduct() (Product, error) {
  product, err := b.constructPartA()
  if err != nil {
    return nil, err
  }
  product, err = b.constructPartB(product)
  if err != nil {
    return nil, err
  }
  return b.finishConstruction(product)
}

In the actual code, the GetFinishedProduct function does a lot more, but the idea is the same: GetFinishedProduct calls several functions in a specific sequence to build the final product from parts that were added to the builder earlier on. The sequence of GetFinishedProduct was obscured by all the error-handling code.

To make it clearer, I added a central error field to the Builder, and then adjusted my “construction” methods to check that error field before they did anything. If the error field was non-nil, the methods simply did nothing. As a result, my GetFinishedProduct could simply call the construction methods in sequence without worrying about their errors. It looks like this:

type Builder struct{
...
  err error
}

...

func (b Builder) constructPartA() Product {
  ...
  product, err := doSomeWork()
  if err != nil {
    b.err = err
    return nil
  }
  return product
}

func (b Builder) constructPartB(p Product) Product {
  if b.err != nil {
    return nil
  }
  ...
}

func (b Builder) finishConstruction(p Product) Product {
  if b.err != nil {
    return nil
  }
  ...
}

func (b Builder) GetFinishedProduct() (Product, error) {
  product := constructPartA()
  product = constructPartB(product)
  product = finishConstruction(product)
  return product, b.err
}

Error-handling is still going on, but it happens in construction functions that tend to be very focused, so they have few lines of code and thus less error-handling to do. Additionally, every construction function stores any of its errors in b.err; this puts the Builder into an “error” state so that future construction functions become no-ops. Each construction function (except the first) checks to see if the Builder is in an error state and simply skips any work if it is.

As a result of these changes, the GetFinishedProduct method becomes far simpler and its construction procedure is more apparent. It doesn’t need to do any error-handling itself since the construction functions are taking care of that. It simply returns whatever the construction functions produced along with the Builder’s error state.

Central Error Handling in a Function

I arrived at a similar approach recently using anonymous functions. I had a scenario where I was creating an implementation of the io.WriterTo interface, i.e. a function with the signature

func (o MyObject) WriteTo(w io.Writer) (int64, error) {...}

One of the challenges with this function was the need to count how many bytes I’d written when I was using other objects to do a lot of the writing and, depending on the methods called, I was getting back an int count of lines written in some cases and int64 in others. This, in addition to error handling, was making the code unwieldy.

What I arrived at was the use of some anonymous methods to help with the accumulation of total bytes written and managing the error-handling in a fashion similar to what I used in the Builder. The final product looked something like this:

func (o MyObject) WriteTo(w io.Writer) (int64, error) {
  total, err := int64(0), error(nil)

  print := func(s string) {
    if err != nil { return }
    var n int
    n, err = fmt.Fprint(w, s)
    total += int64(n)
  }
 
  write := func(wt io.WriterTo) {
    if err != nil { return }
    var n int64
    n, err = wt.WriteTo(w)
    total += n
  }

  // Actual procedure
  print(headerText)
  for _, child := range o.children { // children are io.WriterTo
    write(child)
  }
  print(footerText)

  return total, err
}

Here, the central error is a variable, err, that is accessible to the anonymous functions (assigned to the print and write variables) via closure. As in the Builder’s construction methods, these assign any errors to the err variable and they check to see if that variable is nil before doing any work, becoming “no-ops” if the error is non-nil.

This approach, again, allows the function to express its central procedure more clearly since it doesn’t have to handle errors after every function call.

The driver in this case wasn’t really the central error handling. The real driver was wanting a way to deal with the fact that fmt.Fprint returns the count of written bytes as an int while io.WriteTo returns it as an int64. This required having multiple variables to capture the intermediate counts before adding them to the total. That drove me toward the use of anonymous functions to wrap that work, and once they were in place, it was a simple step to centralizing the error handling.

Conclusion

While I wish that Go gave us the option to use structured exception handling (i.e. try…catch… blocks), there are ways to deal with its native approach without having to write a ton of redundant code in each function. Keep your functions functionally cohesive (i.e. focused on doing one thing) and they’ll have fewer lines of code, and hence, fewer calls that can result in errors. For functions with sequential or procedural cohesion, where the function’s work is more about calling other functions in sequence, look for opportunities to “centralize” your errors so that you can reduce the error-handling and make the procedure clearer.