# Write (Code) for Humans, not for Machines

If you’re a Rails developer, constantize is a very enticing method to use. It allows you to dynamically call the class you need given a string of that class’ name. Take a look at the following snippet; I’ve also commented the code a lil bit to help non-Rails developers understand:

class PaymentController < ApplicationController
  # The valid options
  VALID_GATEWAYS = ['paypal', 'stripe']
  def create
    # Validate the param. Ensure there aren't any tampering from frontend.
    # Camelize the gateway name parameter. paypal => Paypal, stripe => Stripe
    gateway_name = params[:gateway_name].camelize 
    Payment.process(gateway_name, params)
  def validate!(gateway_name)
    raise "Gateway invalid" unless VALID_GATEWAYS.includes?(gateway_name)

class Payment
  def self.process(gateway_name, params)
    # This is the magic. It constructs the class name on-the-fly.
    # If gateway_name = "Paypal", then "PaypalGateway" will be initialized.

class PaypalGateway
  def process_payment(params)
    # process params
    puts "Params processed: #{params}"

class StripeGateway
  def process_payment(params)
    # process params
    puts "Params processed: #{params}"

With the help of constantize, we are wiring Payment class to infer the target class name based on the parameter we passed to it. No switch-statements or if-statements needed. So, should there be a new requirement to allow customers to pay with Braintree, we would just need to create a class called BraintreeGateway, include braintree in the VALID_GATEWAYS array and make sure the form from frontend passes braintree as params[:gateway_name] back to the controller. We have our own little convention here. Life’s good, right?

Well, yeah, for now.

It may seem like nothing can go wrong from here. Of course, the code snippet we have here is a very simple example. A typical production application will have a lot more complex code than this. Having these conventions as a subset of your application, just like in this example, may look cool and easy to work with now, but it will come back and punch the team in the gut.

But how will that happen?

Well, for starters, we are dynamically constructing the target class name with string manipulations. If our new team member Craig, unknowing of our little convention here, comes in and do a project-wide search for PaypalGateway to see where it’s being called, well Craig is out of luck. It would seem as if nothing from the codebase calls PaypalGateway.

This also makes our code more fragile. A simple change to the class name, from PaypalGateway to PayPalGateway or from BraintreeGateway to BrainTreeGateway will break the application. An IDE won't be able to help us catch this error beforehand, because in its knowledge, nothing in the codebase call these two classes.

Not to mention that this convention becomes another thing that new team members will have to be aware of and learn, since it's something specific to our codebase. This slows things down for them. Ideally, we'd want them to immediately be able to contribute to clearing our Backlog.

If this isn't documented, then chances are six months down the road, we, the creator ourselves, are not going to remember about the mechanics of this part, either.

By making conventions like this in our application, we are creating unsafe waters for ourselves and our team members. We're gonna have to document this convention somewhere. That's kind of ironic; We created this convention so that we can do less work when we need to extend the application. Now, it seems there's more work to do and more things to be cautious about.

# Writing code is writing, so pick your audience right. (Hint: It's your team members)

Remember that writing class you took in college? The teacher kept reminding you to always put audience in mind when writing. The same goes with writing code. We are not writing code for machines. We are writing for humans: for ourselves and for our fellow team members. From the start of our career as a software developer till the end, the tools and the languages we might use vary from one time to another. But in between all these changes, there is one constant:

We are always going to work within a team.

Hence, it makes sense to spend a lot of time improving the way we write code for the team, regardless of what language we use. It will help our team to quickly jump into the code and work on it instead of having to spend halves of the time headscratching on how the heck does this and that thing work?

Put simply, here's the goal we want to strive towards:

Maintainers of the application should be able to comprehend and reason about the code without actually running the code.

It sounds simple, but it is really a hard thing to do. If the code we write requires our team members setting up breakpoints here and there and running the application in debug mode just to see what's the state of the application at runtime or what's the flow of our code like, we have failed.

I am still struggling with this goal. Sometimes I am guilty of writing bad code myself. Sometimes, I let bad code slip into the codebase by not scrutinising my team members' code enough in Pull Requests. But throughout my career as a software developer, here are a few tips that I've learnt based on my experience:

  1. Good code is simple and dumb

Good code is straightforward. Good code is dumb. Good code is explicit. Good code spits out clearly what it is trying to do. Good code is obvious. Good code is readable and clear.

Write dumb code.

  1. Create small objects (or functions)

When you break your code into many byte-sized (pun intended) pieces, your code becomes easier to comprehend. Many design principles push for this (e.g. SOLID principles by Uncle Bob, YAGNI, KISS, etc.). One of my favourites on the list is Sandi Metz' Rules for Developers. It is a really handy guideline to keeping your code organised in small bite-sized chunks. One of my colleagues at ServiceRocket shared this video by Sandi Metz that I think we should all see in order to understand how beautiful it is to always make small objects:

  1. Use tests to communicate your code's intent.

Apart from warning you of regressions, tests are also specs for your code. Don't be afraid to be explicit in tests. Write clear and concise spec descriptions that will help you and your team members understand the test. Those extra bytes of strings won't go to your end users anyway.

  1. Do not reassign variables.

This is fundamental in the functional programming paradigm, and it's a good safeguard against bad programming practice. Reassigning variables will leave your team members to wonder what's the supposed value of this variable at a given line of code, and they will have to run the code to know the actual value at runtime. That is exactly what we do not want. You will realise many languages have assignment operators that prevent reassigning values to variables, like Scala's val and JavaScript's const. Use them by default. In languages that don't have this, just always remind yourselves never to reassign a new value to existing variables.

  1. Not naming variables clear enough.

If you find naming variables a hard thing to do, you're in the right direction. It shows that you already have the intention to write for humans. Don't take shortcuts for this, and don't be lazy. There are plenty of tools to help you be productive with long variable names. IDEs and code editors nowadays have autocomplete features that can help type these long words for you. There are no excuses for this.

# Good code is hard to write...

…but the good thing is, we don't have to start from scratch. Prominent software developers like Sandi Metz, Uncle Bob Martin, Kent Beck, Martin Fowler all have written for us plenty of books and articles on what we ought to do to write good code. The only thing left for us is to learn all these and keep on practicing.

Good luck!

Last Updated: 12/30/2019, 7:11:50 PM