Moments of Programmer Clarity

We all love moments of clarity. That moment when things just click. I’ve had a few of these in my life time and now I’d like to share one with you. What feels like a very very long time ago I watched Brian Mann’s backbone on rails series. I’m a bit of a tight ass so it was a big deal for me to spend money on these videos but I’m glad I did. During the hour long easy to follow explanation of how Mann organizes his application, it was a single picture that made things click. Ah… that prized moment of clarity. I’ll share the picture with you.

A Momemnt of Clarity

So a picture is worth a thousand words but without some context here you probably don’t understand the big deal about this picture. You probably look at it and brush it off like some outdated UML diagram or math equation. Allow me to explain how this picture changed my thinking. Before watching the Mann videos, I had been thinking in MVC. I used Rails and Laravel models, views, and controllers. Anything else was pure chaos. Oh, how I desired to confirm to convention over configuration. I would fight tooth and nail for convention over configuration. However, the problem I kept running into though was as my applications grew, so did my models, views and controllers.

During my Java programming days I had usually thrown stuff in the src/ directory with little thought. I whimsically named classes and they often had many responsibilities. The results were typically large bloated classes that I dreaded visiting. I think that is why, partially, when I got bit by the MVC bug, I fell hard. I was in love with MVC. It was the true path to organizing my application. It just made sense and gave my programming direction. This is where the database stuff goes, this is where the routes go, this is where the html goes and I just sprinkled the business logic in controllers and wherever it needed to go. So let’s look at our image again.

A Moment of Clarity

In this application we have three parts Users, Leads and Appointments. Using Rails we could scaffold out some CRUD (create-read-update-delete) for the models, views, and controllers but it is rarely that simple. Notice in this example, when you show a user you need to view Users and Leads and also list Appointments for that user. This is how real world applications work. Despite our best attempts to separate models, views and controllers into CRUD actions, things will over time end up all mixed and mangled together.

Here is the lesson learned from my moment of clarity.

When you have a complex problem get help

It’s simple when you think about it. If your controller is taking on too much water (responsibility), the controller can ask for help from other class. Delegate the hard work. In this case, Brian goes on to say we should only ask for help from our parents. What is a parent? It is the successor who reigns over your family tree. In the case of this example image: Users and Application are parents to Users.Show and Application.List methods respectively. This means that Users.Show must not know about Appointments.List.Instead Users.Show communicates with the global message broker that his parent provides to him, App.request('appointment.listings'). Users.Show has no clue who will answer the appointment.listings request but someone will.

Meanwhile in Cyberland, the Appointment component has already told it’s parent (App) that he will handle any requests for appointment.listings and thus later he receives an anonymous call from User component and responds to it. This is all in the context of Marionette, but the concept applies across all programming languages: when dealing with complex issues, ask for help. Let’s illustrate our principle using a Laravel controller

require_once('./lib/Stripe.php');

class UserController extends Controller
{
	public function store()
	{
		$rules = [
			'email' => 'required|email|unique:users:email',
			'password' => 'required|min:8|confirmed',
			'first_name' => 'required',
			'last_name' => 'required',
		];

		$validator = Validator::make(Input::all(), $rules);

		if ($validator->fails())
		{
			return Redirect::back()->withErrors($validator)->withInput();
		}

		Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");

		Stripe_Plan::create(array(
			"amount" => 2000,
			"interval" => "month",
			"name" => "Amazing Gold Plan",
			"currency" => "usd",
			"id" => "gold")
		);

		$customer = Stripe_Customer::create(array(
			"card" => Input::get('stripeToken'),
			"plan" => "gold",
			"email" => Input::get('email'))
		);

		$user = new User;

		$user->email = Input::get('email');
		$user->password = Input::get('password');
		$user->first_name = Input::get('first_name');
		$user->last_name = Input::get('last_name');
		$user->stripe_id = $customer->id;
		$user->save();

		$interests = new UserInsterest;

		$interests->user_id = $user->id;
		$interests->football = Input::get('football_checked', false);
		$interests->basketball = Input::get('basketball_checked', false);
		$interests->baseball = Input::get('baseball_checked', false);
		$interests->mma = Input::get('mma_checked', false);
		$interests->soccer = Input::get('soccer_checked', false);
		$interests->golf = Input::get('golf_checked', false);
		$interests->tennis = Input::get('tennis_checked', false);

		$interests->save();

		$accounts = UserSocialAccount::where('email', $user->email)->get();

		foreach ($accounts as $account)
		{
			$account->user_id = $user->id;
			$account->save();
		}

		$data = [
			'user' => $user,
			'some_message' => 'You are awesome!',
		];

		Mail::send('emails.welcome', $data, function($message)use ($user)
		{
		  $message->from('please-reply@yoursite.com', 'Some Dude');
		  $message->to($user->email, $user->first_name)->subject('Thanks for signing up!');
		});

		Session::flash('message', 'Welcome to the site!');
		return Redirect::to('/');
	}
}

There is a ton to look at here. This controller is doing too much work on his own. The code all works as expected but it is hard to work with and troubleshoot. Thus when the boss asks you to add that extra cool feature - you know… the one where if a user picks certain interests then they should get different types of emails - where do we shove that logic? The code keeps piling up in one spot. So let’s re-factor this code. First I’m going to look for return statements. Next I’m going to make a statement: returning a redirect, response and setting session messages is the primary responsibility of a controller. The secondary responsibility is to reach out to his friend that best handles the job for this particular action (route). Thus our UserController transforms into this:

class UserController extends Controller
{
	public function store()
	{
		try
		{
			$user = Users::createNewUser(Input::all());
		}
		catch(ValidationException $e)
		{
			return Redirect::back()->withErrors($e->getValidator())->withInput();
		}

		Session::flash('message', "Welcome to the site {$user->first_name}");

		return Redirect::to('/');
	}
}

All we did was ask for help from another class called Users, specifically the createNewUser method. To handle validation we use exception handling but if you don’t like the way this looks then you could get rid of the try ... catch statement and replace it with an if statement. Because validation is something that is done so often in an application, I like to use a little Laravel magic and register an error handler for validation exceptions. I’ll register this error handler somewhere in the bootstrapping of Laravel (i.e. app/global/start.php).

App::error(function(ValidationException $e)
{
	return Redirect::back()->withErrors($e)->withInput();
});

And viola! Now we can remove the try ... catch from our controllers.

class UserController extends Controller
{
	public function store()
	{
		$user = Users::createNewUser(Input::all());

		Session::flash('message', "Welcome to the site {$user->first_name}");

		return Redirect::to('/');
	}
}

Of course I can always catch the exception in the controller if I need fine grained special handling but for the most part, when I get a validation errors, I typically just redirect the user back with error messages and have them fix their form input so this is a pretty sensible default for the entire App.

To wrap things up the idea of seeking help from close friends is one of the basis of Demeter’s Law. I had heard of the Law of Demeter in conversations, but never really gave it much thought when developing. Sometimes really good advise only makes its way into my brain after I fail to follow said advise and a few fails later I can have an epiphany.

When you are faced with complex problem, seek help from others that you trust

post by K.D. on 09/10/2014