Exceptions are your friend, not your enemy
Picture two dancers locked in an elegant waltz across a well-lit dance floor. Their movements, finely honed from practice, glide with precision and predictability, weaving a beautiful tapestry of movement in time with the music. That's your code when defined behavior governs its execution. But what happens when one dancer decides to improvise, unpredictably, breaking the established choreography? Chaos ensues, the dance is ruined but painfully continues on, and our dear audience, the users, are left disappointed and slowly evacuating the theater.
The unpredicted dancer represents undefined behavior in your code. How do we avoid this unwanted surprise? We do so by ensuring that our dancers (our code) stick to the choreography (defined behavior). And just like a well-choreographed dance, our code becomes easier to manage, test, debug and understand. At some point in our programming journey, the shepherd's hook (our exceptions) were employed to yank mis-stepping dancers (our code) off the stage. However, of late, these hooks have been gathering dust, unfairly labelled as "draconian." My hope is that, after this enlightening blog post, you'll feel inspired to dust off those hooks, re-employ them into your code dance, and ensure that the choreography (our defined behavior) remains on point!
Avoiding Undefined Behavior by Defining Behavior
An important part of a programmer's job is to anticipate and guard against undefined behavior. Here's a simple example. Let's say we have a function that sets the current health of a character. In the world of health bars, negative numbers don't exist. So, what happens if a negative number is passed to this function? Hello, undefined behavior!
We can guard against this scenario by explicitly defining what happens when a negative input is encountered. In C#, for instance, we could use a uint (which only accepts positive values) as an input parameter. Alternatively, we could throw an exception if the input is negative, effectively defining the behavior in the face of an inappropriate input. Here's a quick example:
//The "old" way of doing things. public void SetHealthCount(int health) { //Exceptions are not evil! This just stopped really werid bug or even a corrputed game save! if (number < 0) throw new InvalidArgumentException(nameof(health), "value must be non-negative number."); HealthPoints = health; } //The "new" way of doing things. public void SetHealthCount(int health) { //This does not define behavior very well, we are actively permitting hacky code like: SetHealthCount(-1); HealthPoints = health < 0 ? 0 : health; }
By doing this, we've built enforcement against an undefined behavior, thereby avoiding bugs and confusion further down the line.
Spotting Bugs Through Defined Behavior
There's something satisfying about being able to spot a bug from a mile away, like a seasoned park ranger spotting a mischievous raccoon trying to sneak away with your picnic basket. By defining behavior explicitly in our code, we empower ourselves to become such expert bug trackers. In the code seen earlier, a negative number somehow getting in would be obvious to spot and easy to trace.
When behavior is well-defined, exceptions and error messages become our roadmap to finding issues. They point us directly to where something broke down and often provide valuable context about why. This is far more useful than a nebulous error message that leaves us guessing or, worse, undefined behavior that doesn't announce itself but quietly corrupts our program's state.
Embracing Exceptions, The Guardians of Your Code
There's a certain trepidation that programmers have about exceptions. Like an actor forgetting their lines mid-performance, an unhandled exception can bring your program to a grinding halt.
But exceptions, when used correctly, can be a powerful tool. They act as our safety nets, catching instances of undefined behavior before they escalate and corrupt the rest of our program. Instead of fearing exceptions, we should embrace them as a crucial component of robust code.
Consider them as vigilant sentinels guarding the gates of your program, ensuring that only defined behavior gets through. When they encounter undefined behavior, they blow the whistle loudly and immediately, drawing attention to the problem so it can be fixed.
Remember our set health count function from earlier? By throwing an exception when a negative input is encountered, we've effectively guarded against an undefined behavior, stopping the program before it can produce an erroneous result where it could cause further undefined behavior, a hard crash, or a bad user experience.
Conclusion
Programming is as much an art as it is a science. It's like choreographing a complex dance where all steps must be clearly defined and understood by all participants (your code). And as choreographers, we should ensure that every possible action, every twirl, every leap, and every step of our dancers (code behavior) is well-defined.
By defining behavior and guarding against the undefined, we create predictable, maintainable software that's less prone to bugs and easier to debug when issues do arise. So, let's embrace the dance of defined behavior, guide our performers with precision, and create a ballet of code that dazzles our audience (users) with its robustness and reliability.