Published on May 06, 2014
Break up giant functions and make your logic beautiful using Ember.computed
- Ember.computed API
- Extra Macros
- The Frontside (Note: I no longer work there... but they're still awesome and you should hire them!)
If you've been doing Ember for any length of time, you've probably become very familiar with computed properties. The canonical example is combining a first name with a last name to get a full name. Any time first name or last name changes, the full name property is updated automatically. More generally, if one of the properties in the .property list changes, the entire property recalculates. This makes writing complicated logic much easier since you don't need to worry about sending and receiving events. The resulting code is also much more efficient since this is basically a form of caching. However, even with all these conveniences, it can get out of control.
Here's an example taken from a punk rock game world in which can kicking is both a felony and a highly desirable activity for teenagers. This is ugly, hard to read and easy to screw up on editing. You might think that our comment will be helpful since it does describe fairly well what's happening in the function. But what if someone decides that you need both feet to kick the can? Now the comment in the code are saying different things. After just 1 or 2 of these, the comment will be terribly wrong and will do more harm than good.
Let's see if we can make this better. Our goal is to make this code easy to understand, easy to edit and as short as possible. A good sign that we've accomplished that is if we feel that the comment isn't necessary anymore.
We'll tackle this factor in 2 steps. The first will be to break up the property into logical sub-properties. This will make it easier to read and edit, but it will still be long and not as pretty as it could be. The second will be to use Ember.computed macros to make our well-factored code shorter and more beautiful.
Let’s start by extracting the userHasAFoot functionality. You'll notice that this piece of code is basically saying that the user has a foot, but you have to look closely in order to see that it says that. After first creating an alias for the user, then we use a function to encapsulate the logic. This is going to be a pattern. We're going to take a piece of logic that was hid in within a bigger function and put it into its own function, and then we use the name to describe what that logic says.
Here some more functions where we did that. userCanSeeCan is making sure that the length of the cans array in the user's area is greater than zero. This is much easier to read than this, especially when it's in a larger function. We're also doing the same for the copPayingAttention whether the cop is in the user area and whether the cop is not a problem.
When we combine all those into the original canKickCan function, you'll see that it's much easier to read, like a lot easier to read. As a matter fact, it's even easier to read than the old comment and it's actual working running code. If you go and look up userHasAFoot, you can check to make sure that, that function actually encodes the user having a foot. It's easier to read and it's verifiable, making it way better than the vast majority of comments.
The downside is this is pretty long and the functionality that we're encoding is just really about 1/3 of the code that we've written. That's where Ember.computed comes to the rescue. It takes away all the boilerplate and just leaves our business logic.
Let's go through this one by one. Ember.computed.and takes any number of arguments and then it puts a Boolean and between them. Here we're using a 3, but we could use 2, we could use 8. It just strings them together with a Boolean and. We've taken something that's 3 lines. We've boiled it down to 1 line.
Here, we're using the alias computed macro. This is for when we have a property and we're just renaming it; giving an alias.
Here we have Ember.computed.or, which is like ‘and’, except it uses the Boolean or instead the Boolean and.
Here we have the notEmpty computed macro. This is a bit more complicated than the previous ones because here we're using it to check whether an array is empty, but it can also check to see if a string is empty or if a function is empty or if it's nil. You can give it anything and it will check to see whether it fulfills any of the empty conditions, and you don't have to know whether it's a string or an array or a function.
Here we have the gt computed property. That stands for greater than. If cop.attention is greater than 5, this will return true. Ember.computed also has lt, which stands for less than; and gte, which stands for greater than or equal to; as well as lte, which stands for less than or equal to.
Here, we're trying to use the equal computed property. However, if we do it like this, this is going to cause a problem because while this says equal and this is the equals, it doesn't actually work like that. How it works is it takes a variable name as the first argument. The second argument it's going to use just literally. Here, it's checking whether the state is equal to sleepy. If you set the state to sleepy, napTime returns true. If you set the state to something else, then napTime returns false. So we can’t actually use this to compute cop and user area. We're just going to have to leave that as a manual computed property.
This is the downside of having stuff that's named like this because if you have a different understanding of what equal means than the author of the library, then you can run into problems. But usually, it's a great thing.
You can even combine 2 to make up functionality that is not built directly in. Here we're taking an and, which is encapsulated in copAProblem. Then we're negating it to get a copNotAProblem. Even though we had to use 2 Ember.computeds, this is, I think, better factored than this because this ‘not’ here is easy to miss and stands as a separate logical operator than the rest of it.
We end up having a much better factored piece of code, where almost all the code here is actual logic and not boilerplate. There are no comments needed because the code is self-documenting.
In this video, we took 2 steps. First, we broke up our huge function into logical sub-properties. Then we used Ember.computed to make those sub-properties beautiful. In real life, you don't have to do it in 2 steps. You don't have to write out the function .property before turning into in Ember.computed, which can go straight to an Ember.computed.and or whatever you need to use for the occasion.
If you want to look at all the computed macros available to you in Ember, go to this URL. This is for the first of the Ember.computeds. If you scroll down, you'll get to the rest of them. If you want access to more macros, you can use the ember-cpm plug in, which are some custom-made macros built by James A. Rosen and other contributors.
I hope you enjoy working with these macros. I think they'll make your code a lot more beautiful.
I'll see you next week.
One more thing, this episode has been sponsored by The Frontside. We specialize in Ember.js. We also do Ruby on Rails and CSS. By the fact that I've been using the word we, you might guess: I work there as well. Show us your support and send us an email. Thank you for listening.