Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
I recently asked a coworker why a certain check was being done, and he answered with a shrug and said, âJust to be safe.â Over my career, Iâve seen a lot of code written just to be safe. Iâve written a lot of that code myself! If I wasnât sure if I could rely on something, Iâd add a safety check to prevent it from throwing an exception.
To give some examples, I mean idioms like providing unnecessary default values.
axios.get(url).then(({ data }) => // If the response doesn't have a document, use an empty object this.setState({ document: data.document || {} });})
Or checking that each key exists in deeply nested data.
render() { const { document } = this.state; const title = document && document.page && document.page.heading && document.page.heading.title;
return <h1>{title}</h1>}
And many other idioms. Idioms like these prevent exceptions from being thrown. Used without care, suppressing an exception is like hanging art over a hole in the wall.
At a glance, there doesnât appear to be a problem. But you havenât patched the hole and you havenât fixed the bug. Instead of an easy-to-trace exception, you have unusable valuesâââbad dataâââinfiltrating your program. What if thereâs a bad deployment on the backend and it begins returning an empty response? Your default value gets used, your chain of && checks returns undefined, and the string âundefinedâ gets put on your page. In React code, it wonât render anything at all.
Thereâs an adage in computing, âbe liberal in what you accept and conservative in what you send.â Some might argue that these are examples of this principle in action, but I say disagree. I think these patterns, when used to excess, show a lack of understanding of what guarantees your libraries and services provide.
Data or arguments from third parties
What your code expects from somebody elseâs code is a contract. Often, this contract is only implied, but care should be taken to identify what form the data take and to document it. Without a well understood, clearly documented response format from an API, how can you tell whose code is in error when something breaks? Having a clear definition builds trust.
When you request data from an external HTTP API, you donât need to inspect the response object to see if it has data. You already know that it exists because of the contract you have with your request library. For a specific example, the axios documentation defines a schema for the format the response comes back with. Further, you should know the shape of the data in the response. Unless the request is stateful or encounters an error, youâll get the same response every timeâââthis is the contract you have with the backend.
Data passed within the application
The functions you write and the classes you create are also contracts, but itâs up to you as a developer to enforce them. Trust in your data, and your code will be more predictable and your failure cases more obvious. Data errors are simpler to debug if an error is thrown close to the source of the bad data.
Unnecessary safety means that functions will continue to silently pass bad data until it gets to a function that isnât overly safe. This causes errors to manifest in a strange behavior somewhere in the middle of your application, which can be hard to track with automated tools. Debugging it means tracking the error back to find where the bad data was introduced.
Iâve set up a code sandbox with an example of overly safe and unsafe accesses.
const initialStuff = { things: { meta: { title: "I'm so meta, even this acronym", description: "will throw an error if you break the data" } }};
// And within each component, handleClick = e => { if (this.state.stuff) { this.setState({ stuff: null }); } else { this.setState({ stuff: initialStuff }); } };
The âsafeâ component guards against exceptions being thrown.
const { title, description } = (stuff && stuff.things && stuff.things.meta) || {};
And the unsafe one gets the values without any checks.
const { title, description } = this.state.stuff.things.meta;
This approximates what could happen if an external API starts returning unusable data. Which of these failure modes would you rather diagnose?
Performance and development speed
Beyond that, conditionals arenât free. Individually, they have little impact on performance, but codebase that makes a widespread habit of doing unnecessary checks will begin to use an observable amount of time. The impact can be significant: Reactâs production mode removes prop types checks for a significant performance increase. Some benchmarks show production mode in React 15 getting a 2â4x boost over development mode.
Conditional logic adds mental overhead as well, which affects all code that relies on the module. Being overly cautious with external data means that the next person to consume it doesnât know if itâs trustworthy, either. Without digging into the source to see how trustworthy the data is, the safest choice is to treat it as unsafe. Thus the behavior of this code forces other developers to treat it as an unknown, infecting all new code thatâs written.
Fixing the problem
When writing code, take a minute to think through the edge cases.
- What kinds of errors might happen? What would cause them?
- Are you handling the errors you can foresee?
- Could the error occur in production, or should it be caught during development?
- If you provide a default value, can it be used correctly downstream?
Many of the fixes to patterns like this are to handle the errors you can and to throw the errors you canât. It makes sense to verify that data from an external API comes back in the shape youâre expecting, but if it doesnât, can your app realistically continue? Lean on your error handling to show an appropriate response to the user, and your error logging to notify you that thereâs an issue.
Learning what to expect from your tools is a large part of writing code you can trust. Many times this is documented explicitly, but sometimes itâs only implied. The format of data with a backend API is up to whoeverâs writing that backend. If youâre full-stack, great news! You control both ends, and you can trust yourself (right?). If a separate team controls the backend API, then youâll need to establish what is correct behavior and hold each other to it. A third party API can be harder to trust, but youâll also have minimal influence over what it returns.
When writing React components, you have an even more powerful tool: PropTypes. Instead of scattering checks like a && a.b && a.b.c && typeof a.b.c === 'function' && a.b.c(), you can add a type definition as a static property.
Thing.propTypes = { a: PropTypes.shape({ b: PropTypes.shape({ c: PropTypes.func.isRequired }).isRequired }).isRequired};
This might look a little ugly, but now the component will log an error during development if your data is wrong. The missing data will likely cause its own error to throw afterward, and which of these messages is more helpful?
Warning: Failed prop type: The prop 'a' is marked as required in 'Thing', but its value is 'undefined'.
// or
Uncaught TypeError: Cannot read property 'b' of undefined
External data that changes
Of course, sometimes you will have data that youâre not sure about. It might have keys a, b, c or x, y, z, or the data key might be null. These are good times to add checks, but consider defining them as functions that communicate their intent.
const hasDataLoaded = data => typeof data !== "undefined";hasDataLoaded(data) && data.map(/* ⊠*/);
Well named functions will tell your coworkers down the road why these checks are present. Particularly good names will enable them to make the checks more accurate in the future.
Excessively safe idiomsâââand even well-considered checksâââamount to stopgaps to guard against type errors. PropTypes are easy to add to an existing React codebase but arenât the only option available. TypeScript and Flow are much more advanced tools to verify your data types. PropTypes will save you at runtime, but either TypeScript or Flow will allow you to verify that your code works at build time. Types give you an ironclad contract in your code: if youâre not using your data correctly, it wonât even build!
Types arenât everyoneâs jam, but theyâve grown on me for widely shared, highly complex, or difficult-to-change parts of the code. For the rest of the code, in React at least, PropTypes will help you catch errors more quickly and have more confidence in your codebase.
When a developer does something âjust to be safe,â itâs a hint that thereâs an unrecognized unknown. Ignoring these hints can cause small problems to accumulate into large problems. Know what errors you want when youâre making changes, how to guard against those you donât, and learn to trust your code.
Overly defensive programming was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.