Sometimes handling conditional logic can get messy.
Checking against various conditions in the same statement can eat up a lot of space. It can be hard to read. And if we’re dealing with already lengthy syntax from long variable names your code formatter (which you are using because you are an efficient developer) might do something like this.
const validateForm = (formData) => { if ( !formData.username || !formData.username.length > 0 || !formData.username.match(/\^[a-zA-Z]$/) ) { return alert("Invalid username!"); } };
Now this isn’t the worst. In fact while I was writing this example I realized prettier handles lengthy JavaScript pretty well- I’ll spare you the same example in Ruby.
But like many things in software design “not being the worst” is not the bar we’re shooting for. If we add more logic to the validateForm function we’ll start to see the issue with having such lengthy conditionals.
const validateForm = (formData) => { if ( !formData.username || !formData.username.length > 9 || !formData.username.match(/\^[a-zA-Z]$/) ) { return alert("Invalid username!"); } if ( !formData.email || !formData.email.length > 5 || !formData.email.match(/\[a-zA-Z0-9]@[a-z].[a-z]/) ) { return alert("Invalid email!"); } if (!formData.firstName || !formData.firstName.length > 1) { return alert("Invalid first name!"); } if (!formData.lastName || !formData.lastName.length > 1) { return alert("Invalid last name!"); } };
There’s two ways we can fix this issue.
One simple way to deal with lengthy conditionals is to instantiate a variable.
const validateForm = (formData) => { const usernameInvalid = !formData.username || !formData.username.length > 9 || !formData.username.match(/\^[a-zA-Z]$/); if (usernameInvalid) { return alert("Invalid username!"); } };
This abstraction allows us to simplify the if-statement. Now when you read the logic in this function it’s in plain English and you’ll have no trouble understanding it.
In some cases this is all that’s needed. Depending on the scope you’re working with you can even store boolean variables outside of the function itself.
For example, if we have a simple React component with formData as our state.
const UserSignupForm = () => { const [formData, setFormData] = useState({ username: "" }); const usernameInvalid = !formData.username || !formData.username.length > 9 || !formData.username.match(/\^[a-zA-Z]$/); const validateForm = () => { if (usernameInvalid) { return alert("Invalid username!"); } }; return <ImaginaryJSX onSubmit={validateForm}/>; };
The validateForm
function becomes even simpler!
Sometimes our conditionals are more complicated and we need to perform more logical operations to see if a condition is true.
This is where writing a function that acts as a validator/conditional check can be helpful.
For example if we wanted to ensure a username is not currently in use while we are validating it a separate validation function starts to make more sense than a variable.
/** * Retrieves a user by a given username. * @param {string} username * @return {(Object|null)} */ const getUserByUsername = (username) => { axios .get(`/users?username=${username}`) .then((res) => { const user = res.data; return user; }) .catch((err) => console.log(err)); }; const usernameInvalid = (username) => { const isInvalid = !formData.username || !formData.username.length > 9 || !formData.username.match(/\^[a-zA-Z]$/); if (isInvalid) return true; const user = getUserByUsername(username); if (user) return true; return false; }; const validateForm = (formData) => { if (usernameInvalid(formData.username)) { return alert("Invalid username!"); } };
We’ve created two functions here. usernameInvalid
is the high level validator that will return us a boolean to let us know if the username is invalid. The getUserByUsername
function is the additional helper function to see if the user already exists.
This is great because we are following the Open/Closed principle (we don’t have to change anything in the validateForm
function to change how a username is validated) and it nets us more cohesive, finely-scoped functions which come with all sorts of benefits.
Abstracting out long conditionals into variables or functions saves developers a lot of eye strain. In some cases where more logic is required to check if a condition is true it’s the bare minimum you need to do in order to follow good software design.
Next time you’re having to check various conditions in a single if-statement, consider abstracting!