Complicated by Design

A few posts back, I covered complexity.  I feel like it’s a bit of a milestone in growth as a developer when you start to worry about complexity.  At first, you’re just happy to get something to work.  Sometimes it’s a big mess of spaghetti code that wouldn’t survive even a single refactor attempt- the kind of thing you wouldn’t recognize if you looked at it a week later.  You’d pull from the repo, open the file to edit and think:

“What the hell is this supposed to do?  Who wrote this?”

Then you check the log and find out it was you.  Not a great feeling.  I read over and over that code is really written for other developers.  The machine might find your mess acceptable, but the poor sap two cubes over probably won’t.

So, I felt good when I started worrying about creating a complex and convoluted function.  I started thinking about how to make it easier for others to understand it, and use it in the future.  Developing for other developers- not just to get it done and out the door.

There are a lot of good ways to do this- one of my favorites is using sensible defaults in ES6.  When creating a function in ES6, you can assign default arguments to parameters.  That way, if someone else calls the function and omits an argument, the default kicks in and provides something that will work.  If you’re using a good editor, it will probably even tell you the function’s parameters (and if you’re really lucky and using Typescript, it will tell you the type of argument you should pass- assuming the function creator provided types).

So, a nice example might be for showing a notification:

function showNotification(message:String = 'Default Message', timer:Number = 2000) {
    //code to show notification with message that hides after timer expires
}

Or, you can have your function parameters consolidated into one object.  Anyone using that function passes a config object with any necessary parameters (which can also have defaults in your function call).  The advantage?  The order you pass arguments to the function no longer matters.  If I call the above function by passing the timer first, it won’t work properly.  Order matters when passing arguments to a function- unless you use a config option.  Then, names matter- you’d have to be sure to pass an object with the properties named correctly.

function showNotification(config) {
    const message = config.message || 'Default Message';
    const timer = config.timer || 2000;
    //show message, hide after timer
}
//to call the method
showNotification({message: 'Custom message', timer: 5000});

The default arguments style in this function is also another option: the way it was done before official default argument support with ES6.  The first 2 variables check to see if the config option has a message and/or timer property.  If not, it’s set to the defaults.

Depending on your editor, you might lose the intellesense telling you what should be passed to the function.  It’s no longer individual parameters- it’s now a config object.  A bit of a trade off, but again, if you’re using something like TypeScript, you could create an Interface describing that config object:

interface Notification {
    message: String,
    timer: Number
}

Then, when defining your function, you can give the config option a type:

function showNotification(config:Notification)

And your helpful pop up should be back up and running (at least, it works in VS Code).

There’s a bit of a battle in my own head when writing code these days.  There’s the light side, telling me to make it simple and readable and easy for others to use.  The little Angel on my shoulder telling me to use a fully bracketed if/else block for that conditional.  Then there’s the Devil on the other, telling me how cool it is to get it all on one line- and doesn’t a ternary operator look so good right there instead?

And sometimes, they converge.  For super simple conditionals, typing

let cool = 'cool === 'cool' ? 'yes' : 'no'

is both readable and on one line.  But this contrived example brings me to my current shame.  In Angular 2, you can include logic directly in the html template.  It’s kind of one of the main ‘things’ about the framework.  And it’s great, but it’s led me (once again) to some really crap code in one of these templates.

Our designer created these cool input labels.  Using them is not optional.  Suggesting changes to them is not advisable.  One of the limitations is that the validation error message has to be placed on the data-error attribute of the input element itself (the validation involves translating the position of the label, then showing the error message if necessary).  But a field might have multiple validators on it (example: required and maxLength)- and we want to have a custom message for each.  Going with ‘Invalid Field’ is vague and unhelpful.

But how to assign multiple possible messages to one data-error attribute?  We write Javascript in HTML- that’s how!  Thanks Angular!  Unfortunately, this makes for some very long and complicated conditionals.  Not all JS is allowed in an Angular template- no if/else statements is a key restriction.  So we use the ternary operator.  Below is the final (for now) answer:

[attr.data-error]="(newItem.controls.pay_method_id.errors && newItem.controls.pay_method_id.errors.required ? 'Field is required' : '') || (newItem.controls.pay_method_id.errors && newItem.controls.pay_method_id.errors.maxlength ? 'Max length is ' + newItem.controls.pay_method_id.errors.maxlength.requiredLength + ' characters' : '')"

From the original html (with no options for customizing the error message:

data-error="Error with field"

Ugly code.  Confusing code.  I didn’t want to do it in the template file.  I started by making changes in the component itself, but ran into trouble with the update cycle.  We’d have to make sure the validation updates on every keystroke- handled automatically if you put the validation in the template.

A possible way might be to hook into the form’s valueChanges observable, then update a component variable for ‘error message’ each time, but seems complex and inefficient (and would have to do it for every field with multiple validators in the form).  Putting the logic in the html template is ugly and feels bad, but might be the best solution here (until I find something better- or get help from someone smarter!).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s