1. Simple Control Flow and Recursion
NASA rules state that complex constructs like recursion should be avoided because deep recursive calls can exceed stack memory and cause stack overflows.
Watch section from 0:00 →Engineering video
10:27 · 4 September 2024
NASA rules state that complex constructs like recursion should be avoided because deep recursive calls can exceed stack memory and cause stack overflows.
Watch section from 0:00 →All loops must include hard limits, such as array lengths or max iteration constants, to prevent infinite loops and runaway code.
Watch section from 0:00 →Manual memory allocation on the heap should be avoided to prevent memory leaks; stack memory and function arguments should be used instead.
Watch section from 2:51 →Functions should be restricted to a single printed page (around 60 lines of code) to improve maintainability and testability.
Watch section from 4:00 →The video describes a rule of at least two runtime assertions per function to check arguments and handle failures in a controlled manner, including restarting when appropriate.
Watch section from 4:00 →Variables should be declared in the smallest scope possible to avoid the unpredictable behaviors associated with global state.
Watch section from 7:01 →Developers should check return values of non-void functions and explicitly indicate when functions return void.
Watch section from 7:41 →Tooling and preprocessors like polyfills, compilers, and bundlers should be used sparingly to avoid debugging complexity and production performance issues.
Watch section from 8:07 →Object reference pointers should be limited to one level to prevent accidental mutations and hard-to-track bugs.
Watch section from 9:11 →Code should be compiled and checked with all possible warnings, linter rules, and static analysis tools active rather than ignoring warnings under delivery pressure.
Watch section from 9:49 →The shuttle used around 400,000 lines of code. The International Space Station uses 1.4 million lines of code for inflight operations and 3 million lines of code for on ground support. The curiosity Rover that's exploring Mars uses 2.5 million lines of embedded code. Not only that in space, the margin for error, it's zero. To avoid bugs in these scenarios, NASA developed 10 rules to write better code. In this video, we will apply them to a JavaScript code base and see what can we learn from the space agency that can make us better engineers. Rule number one, simple control flow, basically avoiding complex constructs like recursion. NASA says we should not use recursive functions like this one. As we see, factorial will end up calling factorial again. Imagine we would have a very, very big number, this function can really get out of control. And we would end up with a stack overflow error because we have way too many functions calling each other, all being handled by the limited stack memory. Instead, write every recursive behavior as a loop. In this case, we can write it as a for loop, and also be done with a while loop. Very important for you as a JavaScript developer or a developer interviewing right now, make sure you can translate from recursive function to an iterative approach with a for or a while loop. Rule number two, all loops must have fixed limits. We've seen that we shouldn't be using recursion. But NASA goes even further and says, whenever we have a loop, like in this case, I have a while loop. We will add a hard limit to it. In this case, we look at the while loop, it's basically saying while the element in the array is not equal to a specific target, keep increasing i. In the case in which the target, it's never in the array, this while loop will be infinite and basically the program will collapse. It will fail, it will end up in unpredictable state. Instead of this, NASA says we should add high
limits to our while while loops. In this case, we can use the array length as a hard limit and rewrite this code like this, where we actually go only up to the array length. So this loop can never become what they call run away code. It can never go into like an infinite loop that goes on ever and ever and then the program ends up failing. But, NASA goes even further and says that you can even add a constant of max iterations and have no loop, either that's a for or a while loop, go higher than that.
So basically in every loop condition, you'd actually say never go past the array length in this case, but also if the array length is bigger than the max iterations, stop. Never go more than whatever this constant is. Again, a lot of code NASA writes runs on very limited embedded devices with very limited memory, but this lessons you can extrapolate to our modern application. Going further, rule number three: do not use hip memory.
Well, if you're a JavaScript developer, you would ask yourself, well, what do you mean? I mean, we never allocate memory in JavaScript, and they say you use the stack memory instead. But, if you're using C and you are actually allowed to allocate memory, you're also responsible to free that memory. And the problem is as a developer, you might forget to free it. Now, going back to what is the hip? The hip it's a big space of unstructured memory.
And let's say we end up saving a variable here. If we forget to free it up, that memory will always be busy. And so, imagine we get this big chunk of memory, and we forgot to free it up, we're still referencing, we'll end up maybe running out of memory. And this is why NASA says that is better to use the stack. But what does that really mean? Well, that means that instead of referencing or putting variables in the hip, you should pass them as function arguments.
And when they go past in as a function argument, they go into the stack as the function code and the argument. The cool thing is that whenever this function finished, it will be removed. from the stack, so we know for sure that memory will be freed automatically. So, this is a way for them to tell you that you should not allocate memory manually. Now, if you're a JavaScript developer, this is not really elephant. But you could actually apply this to your function and code. Instead of using global variables that will stay in the heap memory for a long time, we prefer using local ones. That's one of the most important goal. But I want to talk about this goal number three, which is basically passing variables as arguments rather than enclosing them. Basically, in JavaScript, we do this a lot. We might have a function that takes an argument and we using a variable that was enclosed. It's in the same scope and it was enclosed by this function to create a result. The problem is this variable right here will stay in the heap memory for a very, very long time. Instead of this, we could pass greeting as an argument or just declare it as a constant inside the function scope. In this way, we know that whenever great aliens is not being used anymore, the memory gets freed up and there's nothing in the heap. So, this goes a bit against traditional JavaScript way of writing code where we enclose many things and try to avoid closures as they're very heavy on the memory. And by the way, if you are a JavaScript developer interviewing right now or you want to find your technical gaps in order to get to senior level, take our free 10 minutes technical assessment. It will basically show you what are your gaps and how you can close them to get to senior level or help you prepare for upcoming interviews. Link it's in the comments. Let's go with number four. Restrict functions to a single page. That is 60 lines of code. Basically what they say is you shouldn't have any function that is bigger than a printed page. In this case, have a bit of a spaghetti code that calculates some trajectory, and we can apply solid principles to split it into four different functions that can be used independently. This simplifies code and it's basically applying a single responsibility principles from solid. This is something very useful in everyday code. It will make your code more maintainable and more usable and more testable. And a very important skill for interviewing. Number five, use a minimum of two runtime assertions per function.
run time assertions in JavaScript, what we do have are type assertions in TypeScript. Basically what they are saying is that whenever we have a function receiving a certain argument, rather than assuming that in this case A and B will be both numbers, we should actually check that. And you can do this with console.assert. The cool thing about this is that if this assertion fails, your code will actually be able to recover gracefully.
What they call graceful degradation, because whenever one of those fails, they it's a sub routine that will be triggered and the program can fail in a control manner and restart itself. If by any chance this ends up being a string and we do not have these two assertions, we might enter to very unpredictable behavior, which is something no one and not afford. Can you apply this to your current code base? Well, not really.
But just keep in mind that not only you can assert the type of variables at the fall over time with TypeScript, but you can also write manual assertions if you're really writing some critical code and make your code fail if at runtime your function doesn't receive the specific arguments with the specific type. Going forward, number six, declare variables in the smallest scope possible. This basically says avoid having global variables, because global variables are just like global state.
They can be modified by pretty much any other function and this can lead to very unpredictable situations. So try to lower those things to where they're actually being used and only make things available as close as to where they're being used. Lifting them up can cause very unpractical behavior, but also consumes a lot of memory. So rather having this, we could actually lower those constants to their specific functions, and in that way we know that these functions can't affect each other.
So try to keep things as private as possible and put them in the lowest scope possible. Number seven, check the return value of all non void functions, or indicate something really returns void, if it doesn't return anything. Basically what they are saying, you shouldn't use something in a way that is not being specified to use and always make sure you specify explicitly that something turns void.
This is not something you can directly extrapolate to JavaScript. What you can take, something out of this is that you really want to be very explicit sometimes about how your functions behave and make very little assumptions. Number eight, use the preprocessors sparingly. They are referring to the C preprocessors. But how can we bring this to the JavaScript world? Well, basically in JavaScript nowadays, we have all this polyfilling that basically takes our code, right, and adds polyfill in this case, for example, to mimic the spread operator in a browser that doesn't support it.
Our final code will look like this. And so that console log got transformed to this. Now, good luck if there's an error, finding out where was this error exactly. Not only that, but this introduces a lot of performance problems. One of the reasons it's hard to debug JavaScript is because we always need source maps, which are those files that kind of connect these two pieces. So what they are saying is, be careful with all this tooling.
It adds complexity, and the problem here is that the code you wrote... is not the code running in production, which means who knows? It might fail because of unpredictable reasons, or it would be really hard to debug. What you can take out of this is that there is always a price for using code preprocessors, in our case, module bundles like Webpack or type TypeScript compiler or Babel, and be careful with them, use as little as possible.
To make sure your code is easy to debug in production. Number nine, restrict reference pointers to one level. This basically means do not have objects referencing another object, another object, and finally the value. In this case, we have Rocket, and to get the thrust, I go over to my rocket engine, which refers to the record rocket. engines, which basically is referencing back to this. They are saying that even experienced programmers can end up making very big mistakes here, because you can easily end up modifying this value without wanting to.
Which will change this one, and it's just can be a disaster. So, avoid that and really try to reference things directly as much as you can to a one level reference, that means really accessing the object that has the property, then to re-declare it 15 times. And finally, compile with all the possible warnings active. That means if you get a warning, if your TypeScript code fails, if your Linter rules are failing, if your static code analysis, if you're using thing that you have failed, don't just go and pile and try to make it work and ship it to production. I know people do that because we all have delivery pressure, but that's a bad practice.
Fix everything, don't have warnings, don't have errors in your code. It's a very bad habit and there's no point in establishing code quality tools if we don't use them in a CI CD process. This was it Folks, use this in your code and use them as a reference for what good code or a stable code looks like. So this was Bogdan from the senior dev, and I'll see you in the next one.