Or When You Forgot How to Say ‘No’ to Your Stakeholders
Every once in a while, bad things may spiral out of control, in a cascade of low-probability events, each hitting their opposite desired outcome.
I’ll stop with the corporate babble; let’s get straight to the point. The article refers to the CrowdStrike incident. While everybody blames the programmers, testers, clients, Microsoft, I will blame something else. And more specifically, the rapid pace of development that is pushed by and accepted by most parties usually involved in the product process.
Pushing For Faster Release Affects Most Development Lifecycles
Thought experiment: Consider a software product that has had successful launches over the years. In time, the team composition has completely changed from the initial group that envisioned and developed most of the product. Now this might seem normal and not too concerning at first glance, but it actually presents significant challenges later on. Team composition is what makes it or breaks it.
We can blame it on knowledge transfer and documentation, but ‘rapid pace‘ is a constant lurking in the background.
Team Composition, Something We Should Talk About
While creating a new product, writing things down isn’t a top priority because there are other important tasks to focus on. We usually start writing down details near the end of the process or when something urgent comes up. This isn’t a fact on every project, but it’s something I’ve encountered a lot. So the team composition changes, the documentation is mostly lacking, and the senior who replaced another senior may or may not have the same sense of application security or stability. Even if they do, some decisions may be pushed from above. If not, we must say ‘bad-bad developer‘.
“I Don’t Care, Just Make It Happen”
So, we have a product that needs to launch faster for many reasons.
And time is always relative, with the sense of urgency often tied to other business areas, such as meeting quarterly planning goals or addressing financial deficits. I cannot go in detail, because I don’t fully understand how this happens constantly.
Everywhere I look, we see pressure to deliver quickly, rushed decisions, cutting corners, and ultimately compromising the quality and security of the product.
One can assume it’s the discrepancy of business priorities diverging from actual problem-solving, and vice versa. The business side often loses sight of the intricacies involved in creating the product. Conversely, the programming team might not fully grasp the business’s need to pivot strategies quickly. This disconnect can lead to a situation where neither side respects the other’s challenges.
The result? Well, we kind of experience it constantly.
An app can only be programmed so fast, and the delay usually isn’t due to a lack of fast hands on the keyboard. Instead, it’s often because there’s no clear vision of what the final product should look like.
This might trigger some developers reading this, but the reality is you end up rewriting one specific feature that no one uses, two, maybe three times in a course of let’s say a year. And each time it was released, the deadline needed to be met! “We need to think about the users,” said someone who pushed for a feature that no one wanted. End of rant.
Fast Equals Cutting Corners
Normally, when someone is constantly demanding deadlines be met, or setting unrealistic deadlines, the development team usually starts by taking shortcuts more and more, accepting it as the new normal. So we start to cut in:
- Development phase: How to keep up with specific design patterns or consider all aspects of security, accessibility, reusability, maintainability, and scalability when push comes to shove and we need a release ASAP? Honestly, who is to blame in this circle of complacent?
- Testing phase: Whether it’s development testing or QA, the rapid pace affects this phase significantly because there is no time for proper regression testing. If there are, say, 30 minor releases in three months, QA mostly has time to focus on the given feature in the best-case scenario. To keep things afloat, we end up relying heavily on automated testing phases.
Automated Testing: A Great Tool, But Not the Whole Solution
Automated testing is incredibly useful, like a great toolkit to have under your belt. However, going from this to creating an entirely automated lifecycle and taking those tests for granted is truly something else.
Relying solely on automated testing is honestly one of the biggest mistakes in recent years. This includes component testing, integration tests, and end-to-end testing. While these are important, they can only cover the scenarios mentioned in the test cases, which are mostly just happy flow testing.
Maybe an example will help:
1. Write a function that converts a date object to 12hour AM/PM format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function convertToUTFToAmPmTime(date) { let hours = date.getHours(); let minutes = date.getMinutes(); // Determine AM or PM const amPm = hours >= 12 ? 'PM' : 'AM'; // Convert hours to 12-hour format hours = (hours % 12) || 12; const formattedMinutes = minutes.toString().padStart(2, '0'); return `${hours}:${formattedMinutes} ${amPm}`; } // Usage example: const date = new Date(); //result: 2024-08-08T07:09:38.290Z console.log(convertToUTFToAmPmTime(date)); //result: 7:09 AM |
2. Write a unit test for this feature
1 2 3 4 5 6 7 8 9 |
describe('convertToUTFToAmPmTime function', () => { it('should convert 8:00 AM correctly', () => { const date = new Date(); date.setHours(8); date.setMinutes(0); const result = convertToUTFToAmPmTime(date); expect(result).to.equal('8:00 AM'); }); }); |
So, in this JavaScript code, we have a function that works, right? We take a date object (default JavaScript date value, 2024-08-08T07:09:38.290Z) and convert it into a readable format, adding AM or PM. We then created a unit test to ensure that every time we insert a JavaScript date object, it will be converted correctly. However, this approach is redundant and doesn’t verify the code’s safety. These tests may be valuable for refactoring or ensuring that the happy flow, works, but they don’t cover everything.
This will pass most reviews and it will be merged and deployed, but it doesn’t mean it’s good code. Here, “good code” refers not just to clean code or TDD practices but to instructions that give an expected outcome every time.
I just want to send a parameter, convert it into a readable time format.
But the real world happens, and things don’t always go as expected: the parameter may not be present, may not be in the desired format, or altogether it may even be an attempt to insert malicious code, by manipulating the date object. These scenarios not in the slightest covered by unit tests.
For instance, what if the date parameter is sent from the server as ‘null’?
1 2 3 4 5 |
TypeError: Cannot read properties of null (reading 'getHours') at convertToUTFToAmPmTime (:9:21) at :18:13 at mn (:16:5455) }); |
The code just breaks, and you can’t catch this issue without someone actually testing it, trying different inputs, and exploring potential edge cases.
This simple example illustrates a broader issue: working in a stressful environment where you need to deliver constantly versus having the time to test, evaluate, and improve the code.
Here are a few more values that can easily break the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Example with passing leap second date const date = new Date("2016-12-31T23:59:60Z"); console.log(convertToUTFToAmPmTime(date)); //result: 12:NaN AM // Example with passing a non date object: const date = {}; console.log(convertToUTFToAmPmTime(date)); //result: TypeError: date2.getHours is not a function // Example with passing a string: const date = "2024-08-08T07:09:38.290Z"; console.log(convertToUTFToAmPmTime(date)); //result: TypeError: date2.getHours is not a function // Modifying prototype chain Object.prototype.getHours = function() { return 25; // Invalid hour }; const date = new Date(); console.log(convertToUTFToAmPmTime(date)); // Throws an error or produces unexpected output |
To extend the initial code and make it a bit safer, we can start by preventing as many potential issues as possible. In our example, we ensure that we handle only valid date objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function convertToUTFToAmPmTime(date) { if (!(date instanceof Date) || isNaN(date.getTime())) { throw new Error("Invalid date object"); } let hours = date.getHours(); let minutes = date.getMinutes(); // Determine AM or PM const amPm = hours >= 12 ? 'PM' : 'AM'; // Convert hours to 12-hour format hours = (hours % 12) || 12; const formattedMinutes = minutes.toString().padStart(2, '0'); return `${hours}:${formattedMinutes} ${amPm}`; } |
With this verification to check if the date is an instance of the Date class and calling getTime() to ensure it results in a number, we effectively handle all the breaking cases we tried.
When working with data and calculations, we shouldn’t make many assumptions, such as assuming the code coming from a different object, component, server, browser, or input is always valid. It may sound like over-engineering, but this should be the minimum standard for a function (in JavaScript) that converts a date object to a readable format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function convertToUTFToAmPmTime(date) { if (!(date instanceof Date) || isNaN(date.getTime())) { throw new Error("Invalid date object"); } let hours = date.getHours(); let minutes = date.getMinutes(); if (typeof hours !== 'number' || typeof minutes !== 'number' || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) { throw new Error("Invalid date values"); } // Determine AM or PM const amPm = hours >= 12 ? 'PM' : 'AM'; // Convert hours to 12-hour format hours = (hours % 12) || 12; const formattedMinutes = minutes.toString().padStart(2, '0'); return `${hours}:${formattedMinutes} ${amPm}`; } // Example usage try { const date = new Date(); // Replace with various test cases console.log(convertToUTFToAmPmTime(date)); } catch (e) { console.error(e.message); } |
This approach helps ensure our code is robust and can handle unexpected inputs gracefully, by *ahem* throwing errors.
In Conclusion
Fast-paced development has become the new norm in the industry, and this is, in my opinion, one of the worst aspects of software development.
With our eyes focused only on profits, we create and sell products meant to perform various tasks. However, due to losing sight of quality and the pressures we face, we end up becoming the very thing we strive to fight against.
I ended the article like how Batman movies end, cringe.