Milestones and how CodeClimate helped us reach them

Goals are your lifeblood.

If you don't pick a destination, you'll never get there. Even if you have a map, you'll never find your way. You have no target.

A few months ago, we started using CodeClimate. We knew we had some bad code, and bad code comes in many forms.

Some code was just dusty. We hadn't touched it in over a year. As we all know, you learn a lot in a year. If you think code you wrote a year ago is still great, you probably haven't learned much.

Some code was poorly designed. It was a tightly coupled mess. We've written about this recently on how you should opt into your business logic. Tightly coupled pieces of code will cause you to pull out your hair. Avoid it at all costs. You should check out Ruby Science if you currently feel this pain. It's a great eBook written by thoughtbot which we used to further train ourselves into thinking about code in a better way.

CodeClimate helped us set the goal. It gave us a destination. When wet set our goal, our GPA was 2.5.

At first, we were slightly depressed that our code base received such a terrible score. A few bad classes and bad practices were pulling our GPA down. After all, our code base was almost two years old - and every code base incurs technical debt at some point. With client work, it was debt we couldn't always pay back.

When wet set our goal, our GPA was 2.5.

Don't be like us and get depressed. Many of our favorite code bases have relatively poor GPAs. ActiveMerchant is stuck at a 1.7 GPA right now (although this also includes many third party gateways). Discourse is a 2.8. Paperclip is a 3.1. Rails is a 3.4. Devise is a 3.5. Rails and Devise are pretty damn good.

These aren't amazing 4.0 GPAs like you might expect. Every code base has some parts that are complex.

We set a goal of 3.0. It was achievable in the short term (within a few months). It wasn't unrealistic (like 4.0).

To achieve that goal, we did a number of things:

  • We stopped the bleeding. We stopped allowing any questionable code into our code base. We were very strict with this. No longer were we shipping things and saying "we'll come back to this later."
  • We fixed broken windows. Anytime we were adding a new feature or fixing a bug, we'd refactor.
  • We started having weekly technical discussions with our engineering team. We'd talk about accomplishments over the past week and we'd prepare examples for all of us to talk about and work through.
  • We took some risks and aggressively refactored with proper unit tests. We took the time to extract important pieces of our application into smaller, thoroughly-tested units. This was risky in we were refactoring payment processing and handling of student credits.
  • We collected momentum while watching our GPA rise. We did not tolerate decreases in our GPA, only increases.

As it stands today, our GPA is 3.21 and rising.

We've eliminated numerous code smells. We've properly extracted business logic which transformed F classes into A classes. We have better test coverage for the important pieces of our application. The application is more stable overall. We're not afraid of breaking anything. Classes are better loosely coupled. We've eliminated most ActiveRecord callbacks (and I recommend you eliminate yours, too). Avoid callback hell.

As you can see in the charts above, we still have some work to do. There are still some hairy spots. We still have problems. We work everyday to make our code better as should you.

There's no reason to get depressed though. After all, the most important metric for your product is: Do your customers find it useful? These scores don't mean anything if your product sucks and nobody uses it.

However, improving your scores pay dividends down the road.

  • We're reducing complexity. It's much easier to jump into the code base and understand what's going on.
  • We solve bugs more quickly.
  • It's much easier to refactor for a number of reasons. We have simpler tests. We don't rely on the database much for testing since our business logic is composed of small, simple Ruby classes.
  • We can expand upon existing functionality easier. Things are no longer tightly coupled. If we want to reuse something, it's pretty easy to use it in another place.
  • Our developer happiness has increased. We don't have to work in a shitty code base anymore. Watching our GPA improve is a big boost to our confidence.

We've had some fun over the past few months improving our code. I hope you find this advice useful and start using parts of it in your own teams. Let us know how we can help.

CodeWars shows you there are so many ways to write code

As a team, we've been playing around with CodeWars.  CodeWars is a great way to learn the basics of writing code. They currently support Ruby, JavaScript, and CoffeeScript. We've had some fun with it - discussing the best solution to each problem as a team.

While CodeWars is fun, my favorite part is completing a solution and comparing your answer to the many other answers people have submitted. It's such a great way to learn and you remember where you've come from. You might tell yourself, "I remember when I would've written code like that" or "That's interesting. I never thought about it that way." 

This is completely different from StackOverflow. StackOverflow encourages one accepted answer. How often do you Google for a solution and simply look at the accepted answer? After you work the problem out yourself, CodeWars makes it really simple to scroll through other solutions by real people. 

Here's what I mean. I pulled a few examples for you. I've also given my reaction to each solution. 

Complete the keysAndValues function so that it takes in an object and returns the keys and values as separate arrays.

Simple sort, but this time sort regardless of upper / lower case.

That's why I love CodeWars. I'm not sure if signup is open to everyone, but if you need an invite, let me know. 

The ingredients are not the recipe

Take any can of Coca Cola, and the ingredients are right there on the side. 

But the recipe is famously secret.

The same holds true for companies and organizations. The ingredients might be everything from the computers and software you use to the various positions that people fill.

But it's the recipe that matters.

Proximity

 I'm the first person that will talk (or yell) about how insane it is that so many people aren't leveraging the power of the internet to build internet products and companies.

Our small team builds, maintains and sells a product used by independent yoga studios across the world, we've helped numerous startups launch their ideas, and we've implemented software solutions for billion dollar companies that have eliminated legacy systems with modern software.

And we do this with a team of people across four states, three countries, and two continents.

The notion that you can't do great things when you're not in the same location as the people you're working with is simply wrong, and getting me to believe otherwise would require me to think that I am living my entire life in an imaginary world.

But nothing is free. And as with everything, there are tradeoffs.

Because the internet is so powerful, and because it enables so much, we can forget about how important proximity is. 

We can in fact lean too much on the internet.

Being on a hangout with someone is not the same as giving them a hug. Sharing emoji in a chat room is not the same as laughing over drinks. And reading an email about a concern someone has is not the same as hearing their voice crack in person as they share their fears.

The past few months I've been reminded that getting together with the people that are important to you, in person, is critical to maintaining healthy relationships.

Our team recently had a retreat in Wisconsin where for the first time most of us got together in one place. It was a pivotal moment for us.

One of our programmers who lives in Europe came to the states for 3 months over the summer, and there's no doubt that our relationship is stronger because we were able to spend time together.

I just came back from a visit with an important customer in California and while we work together very well remotely, there are just some challenges that are easier to tackle when you're in the same room together.

The internet is wonderful. That I am able to run an entire business from a laptop, and that anyone who works with us can work from anywhere in the world that they want, whenever they want, is something that I am grateful for on a daily basis.

I truly believe we are living in magical times. 

But it's important to recognize that when it comes to human relationships, the internet *facilitates* connection. It isn't connection itself.

Proximity matters.

Opt-in to your business logic

  Thanks to Bruno for the section on inheritance.

In a legendary post by Bryan Helmkamp, he talks about seven ways you can refactor fat models. All seven relate to extracting code into separate plain old Ruby objects and using them when needed. And this past week, our team had a discussion about inheritance and how it's frowned upon by some of the experts in the Ruby and Rails fields. After really paying attention to all of this and trying to get to the essence of it, here's what I've learned.

The main idea is this: opt-in to your business logic

Opt-in is synonymous with a white-list. Instead of allowing everything and deciding what to exclude (black-listing), you white-list instead. You make a conscious decision as to what should be allowed. You use a white list when you use attr_accessible. You are allowing certain attributes to be mass assigned.

In the case of your business logic, you have to make a conscious decision as to when business logic should execute. 

If you want to save a model from the console, it shouldn't trigger 5 callbacks.

If you have a bunch of callbacks in your model and all of them include some sort of conditional, that's bad. If you want to do some backend work or a migration, you'll have to trace through your code to figure out if the callbacks will run. You have to ask the question, do I even want these callbacks to run? 

Updating a simple field like the name of something should be an easy task.

The exception to this rule is persistence. Callbacks can be managed nicely if you stick to only using them for persistence and avoid using conditionals. If you have a bunch of conditionals on your callbacks, it means they don't execute every time. If your callbacks don't execute every time, they aren't part of the core domain logic and shouldn't be in the model anyway. 

Here's an example of callbacks with conditionals. I'm sure you've seen some code like this before. To figure out which ones will run, you'd have to also look for and understand what the conditionals will return.

Your object should do one thing and do it right every time.

Granularity matters. If you want to opt-in to your business logic, you should be able to opt-in to any combination. 

For example, if you want to post to social networks after a model is created, you should be able to pick and choose which ones you want. If I want to post to Facebook, it also shouldn't post to Twitter.

Take a look at the example below. If you don't want to post to Twitter, you can simply comment out the TwitterPoster and you're done. It's a much better option to keep these separate rather than use a more generic SocialNetworkPoster..

Inheritance is very rigid, usually there is a better option.

If you're writing some code that will be used by a couple of classes in your app, use inheritance only if you absolutely must.

It is common knowledge among folks who practice object oriented design (not just Rubyists) that 'composition should be preferred over inheritance'. In other words, if you were thinking about making a superclass and subclass objects, don't - most likely you can achieve the same thing by making a Ruby module.

Here's a simple example that nicely shows the limitations of inheritance.

If you use these methods and other to opt-in to your business logic, you'll find yourself enjoying your code more. You'll be able to refactor things more easily. You won't find yourself in callback hell and you'll be able to write classes composed of many smaller units. 

Once a unit works, it should work forever. That's the idea. You'll find that you won't have much churn in your units which leads to better code.