Episode #2

How To Avoid Dumb Code Mistakes Part II

Continuing on from part I, this week focuses on easily avoidable mistakes. It teaches techniques like being mindful of interfaces between programming languages, matching of closing entities, and being aware of the availability of functionality in alternative contexts and platforms.

August 02, 2020

Show Notes

No notes available for this episode.

Screencast.txt

Transcribed by Rugo Obi

Tip 5: Do All Your Opening Entities Have Matching Closing Entities?

Do all your opening entities, for example, tags like this table tag, divs, opening parentheses, opening braces, etc. have corresponding closing entities?

Now, for example, if I go to the closing table tag, and I delete that. Let's have a look at what happens to the layout here. I'm refreshing.

You can see the layout is completely broken, all those products are no longer visible.

I would have caught this bug if I were rigorous enough to check that the opening table entity up there in line 27 had a closing entity. And here it does not.

Another way to detect this is to format the code.

And you can see here that the final statement here is indented by two spaces, whereas you'd expect it to be all the way across horizontally. This is indicative of a missing closing entity.

Let me give you another example of where rigor is called for with closing entities: parentheses as it relates to precedence in this case.

So here I have a typical product page. I submit that to Google via a sitemap. This is the code for generating the sitemap. I loop through the IndividualProducts I have in each store I serve, and then I use this custom function, add to add to the sitemap.

And here I calculate the path for that particular product, and I pass in to the sitemap, the lastmod: attribute, and I set that to be the updated_at of a particular product, to tell Google that this particular page was last modified whenever that product was last modified. This is in order to save crawl budget.

So let's take a look at the sitemap generated by this. It actually has a bug. There's no entry in the sitemap.xml for the lastmod.

In fact, the last part is added as an attribute to the particular URL, even though that has no meaning within my website.

So what's gone wrong?

If we look over here, there's actually two functions. First there’s this add function. You don't have to have parentheses in Ruby for functions, but I'll add them here to be clearer.

So we have this add function and then inside that add function we have the revision_notes_path function and this accepts product: and lastmod:.

ruby add(revsion_notes_path(product: product, lastmod: product.updated_at))

However the lastmod: doesn't belong here, the lastmod belongs with the add() function.

So what I really should have done is, add a closing parenthesis here, and removed the closing parenthesis here.

ruby add(revsion_notes_path(product: product), lastmod: product.updated_at)

Now, if I run that code again, I will get the correct sitemap and I've run this earlier and you can have a look at it.

The actual URL doesn't have a lastmod attribute, and the lastmod attribute instead exists within the sitemap as it should.

Tip 6: Are The Constants And Entities Referred To Defined In The Current Context?

a). Are the constants (or other entities) referred to defined in this file?

What do I mean by this?

Let me open up this translations.js file I tend to use.

So you can see here that it's kind of a self-contained translation unit.

I have these English and German translations for various terms, a function to switch the locale, a function to translate a given key and find out what the translation is, and so on. This sets it to a defaultlocale.

So originally I had this in my React Native app, but then I wanted to include it in my React web app.

So I tested it pretty thoroughly in the React Native one, then I copy pasted it into the React web one, and then committed it and sent it to a coworker.

Of course, it did not work in that context. Why? Because I referred here to the i18n module, which is imported here. However, this was not imported within the other project, so I made an assumption about the context which was untrue.

b). Are the constants (or other entities) referred to defined in this environment?

By environment I mean things like development, staging, production, tests, and so on.

Let me give an example of where this went wrong for me recently.

In PHP Laravel, they have these things called AppServiceProvider, this gets called - rather the register function - gets called when the app gets booted, something like that, I’m not so familiar with the internals, it doesn’t matter.

And what I wanted to do here was define a macro for the Browser entity which I use in acceptance testing. This comes via the Laravel dusk package.

And this macro allows me to fill in hidden fields on the screen. This worked perfectly in the test env, however when I tried to run this on production or in staging, the whole thing failed.

Why was that? Well if you look here at the composer.json file (this is kind of the equivalent of the package.json file - It's where you include libraries for PHP) and this laravel/dusk package where Browser’s defined is only available in require-dev, instead of the normal dependencies.

Therefore this constant over here, Browser is only available in dev and testing, whenever those dev dependencies are loaded.

Therefore, this will fail in the typical case. The way around this is to wrap the whole thing in an if statement.

Basically something like “if the environment is test, then you can define that macro”.

c). Are the constants, or other entities referred to defined in this namespace?

So I'm going to pop over to the SendInvitations.php file over here.

The first important thing to note is that this is contained within the app/jobs namespace. It is not top level.

Anyway, this SendInvitation job when it gets called gets called via this handle method.

The first thing it does is check if the event passed to it has invitationSent(). If so, it throws an exception, otherwise it alerts via email and SMS.

This code had a bug in it. The issue is that, even though PHP has an Exception, it is not present within the app/jobs namespace, therefore it failed, with the exception not being found.

What I had to do is say that Exception was contained in the top level namespace like so, in order for it to work. I.e. \Exception

Be aware of what constants are contained within your current namespace.

Lastly,

d). Are the constants (or other entities) referred to defined in this platform?

Okay, for example, in my production website, I have this feature on my downloads page that whenever someone downloads a particular file, this thing comes up saying that the download was accessed.

This is part of fraud control, and you can see the corresponding code right over here, this trackFileDownload() function.

The important part here is that I use the fetch API.

When I first wrote this code I assumed fetch was available on all browsers. However, that is completely untrue.

Let me demonstrate that by opening up the, “Can I use” website for fetch.

Here you can see that it's not supported on Internet Explorer, it's not supported on edge 12/13, not supported on much of Firefox, not supported at all on Opera mini.

Therefore a certain percentage of my users, in fact you can see here, roughly 94% of my users are able to use fetch, 6% aren’t, therefore I had to polyfill this.

An important thing for you to check is whether the features you're using, such as fetch here, are actually available on the platform you're targeting.

This is also true, for example when you use special features on MySQL.

I once programmed an application with a bunch of features, assuming that the latest MySQL will be running on production.

However, there was a limitation there and it could only run 5.7, despite the fact that I programmed in 8, I believe. Therefore we had to get rid of a lot of features. So, I should have checked beforehand if I was being more rigorous, “what does my platform support” before writing any code.

Tip 7: At Language Boundaries, Consider Data Format And Naming Convention Changes.

Point seven, at the boundaries between two languages or systems, are you translating to the correct data format and naming conventions expected on the other side?

Interfaces between systems have a tendency towards breakage that -no pun intended- borders on maniacal. Thus, extra caution is always warranted here.

A common -air quotes- “dumb mistake”, is forgetting to translate data into a format that can be understood by the other side, and accurately preserves meaning.

I'll present a couple of examples of these.

My first example will be interfacing with the command line, a Unix command line.

Many programs call out to other binaries that are available on the command line. So for demonstrative purposes, let me show you a file I have here.

Law and Order.txt, I believe. Let's have a look inside, and you can see the contents of it there.

Now, let's try to work with that file from within Ruby. I'm opening up a pry instance that's basically a repl here.

Now let's assign that file to a variable, we'll call it file. And now I'm going to use the system command to call out to the underlying shell. I'm going to pass it in the cat binary, and then that particular file using string interpolation.

ruby system("cat #{file}")

And it fails. You know that because it returns false.

Let's look at the particular failure.

cat attempts to open Law, and there's No such file or directory. Then a file called and, then a file called order.txt. i.e., it's splitting this entire filename into three separate file names or three separate shell words.

The general solution is to be mindful of this interface between Ruby and the underlying shell.

Most programming languages have utilities for passing arguments to the shell. One that's available in Ruby is this thing called Shellwords.escape.

So if I call that here on the file argument, everything works as expected.

What this does is wrap up the overall file name in a shell word — a single instance, so to speak — and that makes everything work as expected.

You could of course do something simpler here, like wrap the whole thing in quotes, as I'm doing here. That will work, although that's not as general a solution to this class of problems.

Another example of this kind of error is assuming that data types that look similar in one program, the sending program, can be correctly received by another program receiving that data.

In this file, we have an array of objects with either strings or numbers as their values.

The syntax for creating this is identical in Ruby, in Python, and JavaScript.

This leads to the temptation of sending this data, exactly as is to another program.

This however will not work, because a Ruby array is encoded differently and has a different set of bytes than a Python or a JavaScript array.

Therefore, when you try to dump that array, out of a program into another, it just won't work. You have to rely on some sort of data transfer medium like JSON and so on. At least most of the time.

Let me give you an example.

Here I've added a product to my cart. In order to pass the payment details to my client-side PayPal implementation. I need to get some data from my backend into this page.

Here you can see I've attached some JSON to this window.oxnotes.payment variable, you can see it here.

I can also access that directly within JavaScript. Here's window.oxnotes.payment, you can see it has application_context, intent, payment, some transactions, all that kind of stuff.

This corresponds to this bit of code in my back end. I have a script tag, where I run some JavaScript. window.oxnotes.payment equals the HTML raw version of this @payment bit of JSON.

Let's see what happens if I just send a Ruby array to the browser instead of explicitly transforming it into JSON, as I currently do.

So I'm going to open up my payment gateway file. There we go.

And this is the function I'm using to generate that payment object that I'm sending to JavaScript right now, and I called .to_json on that.

So let me delete that and then go back to the browser and refresh and see what happens.

I get an Uncaught SyntaxError. If I try to access the window.oxnote.payment, it doesn't work.

Let's have a look at what's created here, it looks very weird and different. It's using an odd Ruby syntax for the object, that isn’t compatible with JavaScript. Basically, it’s just created a mess.

You've got to take data interchange seriously when you're being rigorous about programming.

Ideally, you should use a first party tool like the to_json method I have there, or some sort of other core language feature.

There are a lot of subtleties you might not expect when transforming data into JSON or some other sort of interchange format. For example, I was once tripped up when passing a string containing new lines to JSON, without using a transformer.

This is because JSON reserves the newline character internally and therefore you need to escape it. A bit of rigor here could have helped me avoid this issue.

Last but not least, I want to bring up something related, watch out for naming convention differences between languages.

For example, in a lot of backend code - Ruby included - snake_case is used.

For example, item_list. Whereas in the JavaScript world, programs expect a camelCase, something like this; itemList.

If you accidentally call the camelCase version in JavaScript land, without having transformed your Ruby data to have the alternative naming convention, then you're going to get a failure.

This is a very common mistake.

The way I get around that is to automatically transform the keys of objects into camelCase at the seams between my Ruby, and JavaScript.

For example, all incoming data that gets used, gets transformed using this function here, camelCaseKeys.

Let me give you an example on the now fixed window.oxnotes.payment JSON.

You can see here, it's now in camelCase. It's applicationContext instead of "application" underscore "context". brandName, and so on and so forth.

In Oxbridge Notes I do that transformation on the front-end. However, in another one of my projects, I do this in the backend from the API.

Before returning any JSON, I call parent: :camelCaseKeys, and you can see the definition for this here.

I haven't a strong preference for where this occurs, what matters is that it should recur transparently, and that you never have to really think about it.

That's all for today, see you next Sunday.