Skip to content

General coding

How the way I work/code/investigate/debug changed with time & experience

I use this metaphor when describing how I work these days.

TL;DR;

  1. Quick feedback is king
    • unit tests
    • quick test in another way
    • reproduce issues locally
    • try things out in a small project on the side, not in the project you're working on
  2. One thing at a time
    • experimenting
    • refactoring preparing for feature addition
    • feature coding
    • cleaning up after feature coding
  3. Divide problems into smaller problems
    • and remember - one thing (problem) at a time

Example

You're working with code that talks to a remote API, you want to test different API calls to the remote API.

don't - change API parameters in code and run the project each time you test something. It takes too long.

do - write a piece of code to send an HTTP request, fiddle with this code

do - intercept request with Fiddler/Postman/other interceptor and reissue requests with different parameters


Example

Something fails in the CI pipeline.

don't - make a change, commit, wait for remote CI to trigger, see result

do - reproduce issue locally


Longer read

  1. Quick feedback
  2. do - write a test for it
  3. do - isolate your issue/suspect/the piece of code you're working with
    • it is helpful if you can run just a module/sub-system/piece of your project/system
    • partial execution helps - like in Python/Jupyter or F# fsx
  4. if you rely on external data and it takes time to retrieve it (even a 5-second delay can be annoying) - dump data to a file and read it from the file instead of hitting an external API or a DB every time you run your code
  5. don't try to understand how List.foldBack() works while debugging a big project. Do it on the side.
  6. spin up a new solution/project on the side to test things
  7. occasional juniors ask "does this work this way" - you can test it yourself easily if you do it on the side

  8. One thing at a time

  9. separate refactoring from feature addition
  10. fiddle first, find the walls/obstacles
  11. git reset --hard
  12. refactor preparing for a new feature (can become a separate PR)
  13. code feature
  14. if during coding you find something that needs refactoring/renaming/cleaning up - any kind of "WTF is this? I need to fix this!" try a) or b)
    • a) make a note to fix it later
    • b) fix immediately
      > git stash
      > git checkout master
      > git checkout -b fix-typo
      fix stuff
      merge or create a PR
      git checkout feature
      > git merge fix-typo or git rebase fix-typo
      continue work
      
  15. always have a paper notepad on your desk

    • note things you would like to come back to or investigate
    • it gives me great satisfaction to go through a list of "side quests" I have noted and strike through all of them, knowing I have dealt with each one before starting a new task
    • when investigating something I also note questions I would like to be able to answer after I'm done investigating
      • example: while working with Axios and cookies I found conflicting information about whether Axios supports cookies. After the investigation, I knew that Axios supports cookies by default in a browser but not in Node.js
  16. Divide problems into smaller problems

  17. example - coding logic for a new feature in a CLI tool and designing the CLI arguments - these can be 2 sub-tasks

Big bang vs baby steps

The old me often ended up doing the big bang. Rewriting large chunks of code at once. Starting things from scratch. Working for hours or days with a codebase that can't even compile.

Downsides - for a long time the project doesn't even compile, I lose motivation, I feel like I'm walking in the dark, I don't see errors for a long time - requires a lot of context keeping in my mind since I've ripped the project apart - if I abandon work for a few days sometimes I forget everything and progress is lost

The new me prefers baby steps

Fiddle with the code knowing I'll git reset --hard. Try renaming some stuff - helps me understand the codebase better. Try out different things and abandon them. At this point, I usually get an idea/feeling of what needs to be done. Plan a few smaller refactorings. After them, I am usually closer to the solution and am able to code it without a big bang.

My recommendations

Terminal etc.

git

node

http

other

It will be great, set it up, don't use it, remove it

2024-05-02

Today, I removed something I allowed to be created despite initially feeling it was redundant.

A year ago, a student found a tool to auto-generate documentation for our internal SDK from code annotations. They proposed embedding this documentation in our Continuous Integration pipeline. Although the idea sounded good on paper, I felt it wouldn’t be used by our team. However, the team was enthusiastic.

Everyone in our team has the SDK's git repo on their machine, and we rely on IntelliSense and the code for documentation. It seemed unlikely that we would change our habits since the new documentation wouldn’t be easier to use than just using F12 to view the source code.

Despite my doubts, I allowed this feature as the team lead. I had already rejected a few initiatives from that student and didn’t want to kill their motivation. I wanted to let them work on something they found interesting. There was a slight chance I was wrong and the documentation might be used.

Today, a year later, I noticed the docs website no longer works. It had been down for some time, and no one noticed because no one used it. I removed any trace of the published documentation.

This made me reflect: was it wrong to allow something to be created that never paid off?

In this case, the investment was small. The gain was that the student got to work on something interesting. So, I think it was right to let our team try it out and remove it once we were certain it wasn’t used

Short post on premature optimization and error handling

Premature optimization is the root of all evil ~Donald Knuth

Don't pretend to handle more than you handle ~me just now

My team took over some scrapers. After a few months an issue is reported stating that accounting is missing data from one of the scrapers. No errors were logged and or seen by our team.

My colleague investigates the issue. Findings:

  • most recent data is indeed missing in our data base (it is already available in the API)
  • the data is often delayed (compared to when it's available in the remote API)
  • the data is not time critical but a delay of hours or days is vexing (remember folks - talk to your users or customers)
  • the scraper is using parallelism to send all requests at once (probably to get the data faster)
  • the API doesn't like our intense scraping and bans us from accessing the API, sometimes for hours
  • we never saw any Errors as the error handling looks like this:
try {
    data = hit the REST API using multiple parallel requests
    persist(data)
} catch {
    log.info("No data found")
}

Take away

  • talk to your users - in this case to learn that this data is not time critical
  • don't optimize prematurely
  • don't catch all exception pretending you handle them

More venting

I have seen my share of premature optimizations. AFAIR I always managed to have a conversation about the (un)necessity of an optimization and agree to prefer readability/simplicity over premature optimization.

If you see premature optimization my advise is "talk to the perp". People do what they consider necessary and right. They might optimize code hoping to save the company money or time.

If you have the experience to know that saving 2kB of RAM in an invoicing app run once a month is not worth using that obscure data structure - talk to those who don't yet know it. Their intentions are good.

I'm pretty sure I'm also guilty of premature optimization, just can't recall any instance as my brain is probably protecting my ego by erasing any memories of such mistakes from my past.

An example

One example of premature optimization stuck with me. I recall reviewing code as below

foreach(var gasPoint in gasPoints)
{
    if (gasPoint.Properties.Any())
    {
        foreach (var x in gasPoint.Properties)
        {
            // do sth with x
        }
    }
}

The review went something like this:

me: drop the if, foreach handles empty collections just fine

author: but this is better

me: why?

author: if the collection is empty we don't even use the iterator

me: how often is this code run?

author: currently only for a single entry in gasPoints, but there can be more

me: how many more and when?

author: users might create an entry for every gas pipeline connection in Europe

me: ok, how many is that?

We agreed to drop the if after realizing that:

We have ~30 countries in Europe, even if they all connect with each other there will be at most ~400 gas connections to handle here. We don't know that the if is faster then the iterator. 400 is extremely optimistic. We have 1 entry now, and realistically we will have 10 gasPoints in 5 years.

The conversation wasn't as smooth as I pretend here but we managed.

https://wiki.c2.com/?PrematureOptimization

https://wiki.c2.com/?ProfileBeforeOptimizing

https://youtube.com/CodeAesthetics/PrematureOptimization