Author Archives: Jason Swett

Why I think live-coding interviews are a good idea

It’s popular to be critical of live-coding interviews. But like many issues that people reduce to “X is good” or “X is bad”, I think the question of live coding interviews is actually more complicated than that. Here’s my take.

The criticisms of live coding interviews

The content isn’t realistic

This can obviously be true. If a candidate is asked in an interview to, for example, live-code an implementation of a linked list or a binary search, then the candidate is being asked to do something that’s not really representative of what they’d actually do in a typical programming job.

Programmers who pass these tests are demonstrating that they’re capable of passing computer-sciencey type tests, but they’re not necessarily demonstrating that they have skills that match the job they’re interviewing for. So these types of tests aren’t very smart ones.

But to use these as a criticism of live coding tests is a confusion. If many live-coding tests contain unrealistic content, that doesn’t prove that live-coding tests are bad, it only proves that live-coding tests with unrealistic content are bad.

The spotlight and pressure changes the outcome

Sometimes developers choke on live-coding exercises not because they’re not capable of completing the task but because the interview process made them too nervous to think clearly enough.

I do think this is a valid criticism of live-coding tests. Having said that, there is at least one thing that can be done to mitigate the nervousness factor. That is: don’t make it a race. A live-coding test doesn’t have to be “complete X in Y amount of time or you fail”. When I’ve run live-coding tests myself in the past, I limit the time we spend, but I don’t require candidates to finish the exercise as a condition to passing. Rather than “Did you complete the entire exercise?” the question is “Did you get anything to work at all?” Candidates who fail to get anything to work don’t pass the test.

Why I’m in favor of live-coding interviews

End products don’t tell the whole story

Given a particular challenge, two different developers may arrive at roughly the same solution. But one developer may have struggled with the challenge for two hours while the other developer solved it in five minutes. I contend that this difference matters.

If I give developers a take-home exercise, then all I can see is the end product. I don’t get to see how they arrived at the end product. Maybe developer A has 20 years of experience but they approached the task in a slapdash and thoughtless manner and struggled for hours before finally piecing together the right answers. Maybe developer B has only 3 years of experience but methodically homed in on the answer in just a few minutes because they’re smarter.

Selecting for intellectual character over experience

Because end products don’t tell the whole story, judging by end products alone may select for experience over raw brainpower. If the candidate submits a solution that works but looks sloppy, how do you know if that mistake is due to lack of intelligence or just lack of experience?

It would be a real shame IMO to reject someone who’s really smart and has great potential just because at the current moment they’re not very experienced. I’d much rather take an inexperienced but smart person who will be 10X better a year from now than a very experienced but not-very-smart person who is not only slow but also will never get much better.

And as a side note, I think “smart” is way too vague of a word to use when evaluating programmer potential. Plenty of people are “smart” in the sense that they can solve hard logic puzzles or write complicated algorithms, but many of those “smart” people also make shockingly incompetent developers because their intelligence is of a very narrow kind. Other traits that I think make a good programmer include things like intellectual honesty, intellectual rigor, inquisitiveness and discipline. These qualities make a world of difference. And I don’t think they’re very evident in the end result of a take-home coding test.

When scrutiny isn’t necessary

The only reason to do something like a live-coding interview, or any interview process at all, is if you don’t yet know how good the person is and if they’re the kind of person you’d like to work with. If other, better ways are available, then certain aspects of the interview process becomes less necessary. For example, if someone has a large body of publicly available work, or if you’ve already worked with them before, then you can potentially use that to judge the person’s abilities and you don’t have to contrive tests for them. But most programming candidates don’t have much to be judged by, and so unfortunately in most cases we have to do this painful song and dance we call the interview process.

Summary

I think live-coding tests can either be done well or poorly. Live-coding tests have some unavoidable drawbacks, but so does everything. The value of live-coding tests, when done in a smart way, is that they reveal how a person thinks and behaves while coding. To me this is the most important thing to learn about a programming candidate. I don’t see how this information can be gained any way other than actually watching them code.

Abstraction in Rails

If we wanted to, we could, of course, write web applications in assembly code. Computers can understand assembly code just as well as Ruby or Python or any other language.

The reason we write programs in higher-level languages like Ruby or Python is that while assembly language is easy for computers to understand, it’s of course not easy for humans to understand.

High-level languages (Ruby, Python, Java, C++, etc.) provide a layer of abstraction. Instead of having to think about a bunch of low-level details that we don’t care about most of the time, we can specify the behavior of our programs at a higher, more abstracted level. Instead of having to expend mental energy on things like memory locations, we can focus on what our program actually does.

In addition to using the abstractions provided by high-level languages, we can also add our own abstractions. A function, for example, is an abstraction that hides low-level details. An object can serve this purpose as well.

We’ll come back to some technical details regarding what abstraction is. First let’s gain a deeper understanding of what an abstraction is using an analogy.

Abstraction at McDonald’s

Let’s say I go to McDonald’s and decide that I want a Quarter Pounder Meal. The way I express my wishes to the cashier is by saying “Quarter Pounder Meal”. I don’t specify the details: that I want a fried beef patty between two buns with cheese, pickles, onions, ketchup and mustard, along with a side of potatoes peeled and cut into strips and deep-fried. Neither me nor the cashier cares about most of those details most of the time. It’s easier and more efficient for us to use a shorthand idea called a “Quarter Pounder Meal”.

The benefit of abstraction

As a customer, I care about a Quarter Pounder Meal at a certain level of abstraction. I don’t particularly care whether the ketchup goes on before the mustard or if the mustard goes on before the ketchup. In fact, I don’t even really think about ketchup and mustard at all most of the time, I just know that I like Quarter Pounders and that’s what I usually get at McDonald’s, so that’s what I’ll get. For me to delve any further into the details would be for me to needlessly waste brainpower. To me, that’s the benefit of abstraction: abstraction lets me go about my business without having to give or receive information that’s more detailed than I need or want. And of course the benefit of not having to work with low-level details is that it’s easier.

Levels of abstraction

Even though neither the customer nor the cashier want to think about most of the low-level details of a Quarter Pounder Meal most of the time, it’s true that sometimes they do want to think about those details. If somebody doesn’t like onions for example, they can drop down a level of abstraction and specify the detail that they would like their Quarter Pounder without onions. Another reason to drop down a level of abstraction may be that you don’t know what toppings come on a Quarter Pounder, and you want to know. So you can ask the cashier what comes on it and they can tell you. (Pickles, onions, ketchup and mustard.)

The cook cares about the Quarter Pounder Meal at a level of abstraction lower. When a cook gets an order for a Quarter Pounder, they have to physically assemble the ingredients, so they of course can’t not care about those details. But there are still lower-level details present that the cook doesn’t think about most of the time. For example, the cook probably usually doesn’t think about the process of pickling a cucumber and then slicing it because those steps are already done by the time the cook is preparing the hamburger.

What would of course be wildly inappropriate is if me as the customer specified to the cashier how thick I wanted the pickles sliced, or that I wanted dijon mustard instead of yellow mustard, or that I wanted my burger cooked medium-rare. Those are details that I’m not even allowed to care about. (At least I assume so. I’ve never tried to order a Quarter Pounder with dijon mustard.)

Consistency in levels

Things tend to be easiest when people don’t jump willy-nilly from one level of abstraction to another. When I’m placing an order at McDonald’s, everything I tell the cashier is more or less a pre-defined menu item or some pre-agreed variation on that item (e.g. no onion). It would probably make things weird if I were to order a Quarter Pounder Meal and also ask the cashier to tell me the expiration dates on their containers of ketchup and mustard. The cashier is used to taking food orders and not answering low-level questions about ingredients. If we jump among levels of abstraction, it’s easy for the question to arise of “Hang on, what are we even talking about right now?” The exchange is clearer and easier to understand if we stick to one level of abstraction the whole time.

Abstraction in Rails

In the same way that abstraction can ease the cognitive burden when ordering a Quarter Pounder, abstraction can ease the cognitive burden when working with Rails apps.

Sadly, many Rails apps have a near-total lack of abstraction. Everything that has anything to do with a user gets shoved into app/models/user.rb, everything that has anything to do with an order gets shoved into app/models/order.rb, and the result is that every model file is a mixed bag of wildly varying levels of abstraction.

Soon we’ll discuss how to fix this. First let’s look at an anti-example.

Abstraction anti-example

Forem, the organization behind dev.to, makes its code publicly available on GitHub. At the risk of being impolite, I’m going to use a piece of their code as an example of a failure to take advantage of the benefits of abstraction.

Below is a small snippet from a file called app/models/article.rb. Take a scroll through this snippet, and I’ll meet you at the bottom.

class Article < ApplicationRecord
  # The trigger `update_reading_list_document` is used to keep the `articles.reading_list_document` column updated.
  #
  # Its body is inserted in a PostgreSQL trigger function and that joins the columns values
  # needed to search documents in the context of a "reading list".
  #
  # Please refer to https://github.com/jenseng/hair_trigger#usage in case you want to change or update the trigger.
  #
  # Additional information on how triggers work can be found in
  # => https://www.postgresql.org/docs/11/trigger-definition.html
  # => https://www.cybertec-postgresql.com/en/postgresql-how-to-write-a-trigger/
  #
  # Adapted from https://dba.stackexchange.com/a/289361/226575
  trigger
    .name(:update_reading_list_document).before(:insert, :update).for_each(:row)
    .declare("l_org_vector tsvector; l_user_vector tsvector") do
    <<~SQL
      NEW.reading_list_document :=
        setweight(to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.title, ''))), 'A') ||
        setweight(to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.cached_tag_list, ''))), 'B') ||
        setweight(to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.body_markdown, ''))), 'C') ||
        setweight(to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.cached_user_name, ''))), 'D') ||
        setweight(to_tsvector('simple'::regconfig, unaccent(coalesce(NEW.cached_user_username, ''))), 'D') ||
        setweight(to_tsvector('simple'::regconfig,
          unaccent(
            coalesce(
              array_to_string(
                -- cached_organization is serialized to the DB as a YAML string, we extract only the name attribute
                regexp_match(NEW.cached_organization, 'name: (.*)$', 'n'),
                ' '
              ),
              ''
            )
          )
        ), 'D');
    SQL
  end
end

Given that dev.to is largely a blogging site, the concept of an article must be one of the most central concepts in the application. I would imagine that the Article would have a lot of concerns, and the 800-plus-line article.rb file, which contains a huge mix of apparently unrelated stuff, shows that the Article surely in fact does have a lot of concerns connected to it.

Among these concerns, whatever this trigger thing does is obviously a very peripheral one. If you were unfamiliar with the Article model and wanted to see what it was all about, this database trigger code wouldn’t help you get the gist of the Article at all. It’s too peripheral and too low-level. The presence of the trigger code is not only not helpful, it’s distracting.

The trigger code is at a much lower level of abstraction than you would expect to see in the Article model.

The fix to this particular problem could be a very simple one: just move the trigger code out of article.rb and put it in a module somewhere.

class Article < ApplicationRecord
  include ArticleTriggers
end

The trigger code itself is not that voluminous, and I imagine it probably doesn’t need to be touched that often, so it’s probably most economical to just move that code as-is into ArticleTriggers without trying to improve it.

Another anti-example

Here’s a different example which we’ll address in a little bit of a different way.

There are a couple methods inside article.rb, evaluate_markdown and evaluate_front_matter.

class Article < ApplicationRecord
  def evaluate_markdown
    fixed_body_markdown = MarkdownProcessor::Fixer::FixAll.call(body_markdown || "")
    parsed = FrontMatterParser::Parser.new(:md).call(fixed_body_markdown)
    parsed_markdown = MarkdownProcessor::Parser.new(parsed.content, source: self, user: user)
    self.reading_time = parsed_markdown.calculate_reading_time
    self.processed_html = parsed_markdown.finalize

    if parsed.front_matter.any?
      evaluate_front_matter(parsed.front_matter)
    elsif tag_list.any?
      set_tag_list(tag_list)
    end

    self.description = processed_description if description.blank?
  rescue StandardError => e
    errors.add(:base, ErrorMessages::Clean.call(e.message))
  end

  def evaluate_front_matter(front_matter)
    self.title = front_matter["title"] if front_matter["title"].present?
    set_tag_list(front_matter["tags"]) if front_matter["tags"].present?
    self.published = front_matter["published"] if %w[true false].include?(front_matter["published"].to_s)
    self.published_at = parse_date(front_matter["date"]) if published
    set_main_image(front_matter)
    self.canonical_url = front_matter["canonical_url"] if front_matter["canonical_url"].present?

    update_description = front_matter["description"].present? || front_matter["title"].present?
    self.description = front_matter["description"] if update_description

    self.collection_id = nil if front_matter["title"].present?
    self.collection_id = Collection.find_series(front_matter["series"], user).id if front_matter["series"].present?
  end
end

These methods seem peripheral from the perspective of the Article model. They also seem related to each other, but not very related to anything else in Article.

These qualities to me suggest that this pair of methods are a good candidate for extraction out of Article in order to help keep Article at a consistent, high level of abstraction.

“Evaluate markdown” is pretty vague. Evaluate how? It’s not clear exactly what’s supposed to happen. That’s fine though. We can operate under the presumption that the job of evaluate_markdown is to clean up the article’s body. Here’s how we could change the code under that presumption.

class Article < ApplicationRecord
  def evaluate_markdown
    body_markdown = ArticleBody.new(body_markdown).cleaned
  end
end

With this new, finer-grained abstraction called ArticleBody, Article no longer has to be directly concerned with cleaning up the article’s body. Cleaning up the article’s body is a peripheral concern to Article. Understanding the detail of cleaning up the article’s body is neither necessary nor helpful to the task of trying to understand the essence of the Article model.

Further abstraction

If we wanted to, we could conceivably take the contents of evaluate_markdown and evaluate_front_matter change them to be at a higher level of abstraction.

Right now the bodies of those methods seem to deal at a very low level of abstraction. They deal with how to do the work rather than what the end product should be. In order to understand what evaluate_markdown does, we have to understand every detail of what evaluate_markdown does, because it’s just a mixed bag of low-level details.

If evaluate_markdown had abstraction, then we could take a glance at it and easily understand what it does because everything that happens would be expressed in the high-level terms of what rather than the low-level terms of how. I’m not up to the task of trying to refactor evaluate_markdown in this blog post, though, because I suspect what’s actually needed is a much deeper change and a different approach altogether, rather than just a superficial polish. Changes of that depth that require time and tests.

How I maintain a consistent level of abstraction in my Rails apps

I try not to let my Active Record models get cluttered up with peripheral concerns. When I add a new piece of behavior to my app, I usually put that behavior in one or more PORO models rather than an Active Record model. Or, sometimes, I put that behavior in a concern or mixin.

The point about PORO models is significant. In the Rails application that I maintain at my job, about two-thirds of my models are POROs. Don’t make the mistake of thinking that a Rails model has to be backed by Active Record.

Takeaways

  • Abstraction is the ability to engage with an idea without having to be encumbered by its low-level details.
  • The benefit of abstraction is that it’s easier on the brain.
  • Active Record models can be made easier to understand by keeping peripheral concerns out of the Active Record models and instead putting them in concerns, mixins or finer-grained PORO models.

Linux show date and time

In order to show the date and time in Linux, just run this.

$ date

The output will look something like this.

Fri Feb 25 14:39:34 UTC 2022

That’s all.

When I do TDD and when I don’t

Some developers advocate doing test-driven development 100% of the time. Other developers think TDD is for the birds and don’t do it at all. Still other developers go in the middle and practice TDD more than 0% of the time but less than 100% of the time.

I personally am in the camp of practicing TDD some of the time but not all. Here’s my reasoning.

When TDD makes sense to me

It’s not the case that I use TDD, or even write tests at all, for every single project I work on. But I do pretty much always program in feedback loops.

Feedback loops

The “feedback loop method” is where work as follows. First, I think of a tiny goal that I want to accomplish (e.g. make “hello world” appear on the screen). Then I decide on a manual test I can perform in order to see if that goal is accomplished (e.g. refresh the page and observe). Then I perform the test, write some code to try to make the test pass, perform the test again, and repeat the process with a new goal.

TDD == automated feedback loops

The way I view TDD is that it’s just the automated version of the manual work I was going to do anyway. Instead of making a to-do note that says “make ‘hello world’ appear on the screen” and then manually refreshing the page to see if it’s there, I write a test that expects “hello world” to appear on the screen. All the other steps are the exact same.

I’ve found that TDD works great for me when I’m working on what you might call “crisply-defined” work. In other words, the requirements I’m working to fulfill are known and specified. I find that I’ve found that there are other scenarios where TDD doesn’t work so great for me.

When TDD doesn’t make sense to me

Coding as production vs. coding as thinking

It’s easy to think that the reason to write code is to create a work product. But that’s certainly not the only reason to write code. Code isn’t just a medium for producing a product. It’s also a medium for thinking.

This is the idea behind a “spike” in Agile programming. When you’re doing a spike, you have no necessary intention to actually keep any of the code you’re writing. You’re just exploring. You’re seeing what it looks like when you do this or how it feels when you do that.

You can think of coding kind of like playing a piano. Sometimes you have some pieces of music already in your head and you’re trying to record an album. Other times you’re just messing around to see you can come up with any music worth recording. These are two very different modes of engaging with your instrument. Both are very necessary in order to ultimately record some music.

TDD doesn’t mix great with spikes

I often find that a spike phase is necessary when I’m coding, for example, a feature with known big-picture requirements but unknown UI specifics. In that case my test would be so full of guesses and placeholders that it would be kind of a joke of a test, and it wouldn’t help me much. In these cases I give myself permission to forego the testing during the spike period. I come back after I have some working code and backfill the tests.

Takeaways

  • I don’t practice TDD 100% of the time. (I believe I do practice TDD the vast majority of the time though.)
  • I view TDD as the automated version of the coding workflow that I already use anyway.
  • Producing a work product is not the only reason to write code. Code can also be a medium for thinking.
  • When I’m in the mode of using coding as a way to think, I find that the benefits of TDD don’t really apply.

How to apply a bugfix

Any bugfix job has three distinct stages: reproduction, diagnosis, and fix. This post is about the third phase, the fix.

It might seem like the “fix” phase of a bugfix is simple: just fix it. But it’s not that simple. Many people screw it up. I certainly have.

They key thing with a bugfix is that you have to have a way to be sure your bugfix actually fixed the bug. So before you apply a bugfix, devise a test (which can be a manual test or an automated test) which has the following qualities:

  • The test fails when the bug is present
  • The test passes when the bug is absent

And, crucially, perform the test both before and after the fix has been applied. If you don’t perform the test before the fix is applied, you don’t know if the test is passing afterward because your fix worked or because your test gives a false positive.

And not only is it important to make sure your test fails when the bug is present, but that it fails in the precise way you expect. If the test fails in a way other than what you expect, then it might be failing for a reason that’s irrelevant to the bug, and you can’t be sure that your test is a valid one.

By following this process, you can have a higher chance of fixing your bug just once rather than having one of more false fixes before you finally succeed.

Don’t mix jobs

Many programmers spend most of their working time in a very wasteful manner.

One of the biggest sources of waste is the mistake of mixing jobs. In this post I’ll explain what I mean by mixing jobs, why it’s bad, and how to avoid it.

Mixing the “what” with the “how”

When you write a line of code, you’re actually carrying out two jobs. One job is to decide what you want the line of code to do. The other job is to figure out the exact syntax to accomplish your goal. You could call these two jobs “what to do” and “how to do it”.

Mixing a “main job” with a “side job”

Another form of mixing jobs is to start working on a certain job, then notice some semi-relevant problem or opportunity along the way, and immediately start addressing the new job in a way that’s not separate from the original job.

Why mixing jobs is bad

Almost everything we do in programming is to accommodate the fact that human brains are weak and frail in the face of the staggeringly complex task of building and maintaining software systems. If humans were infinitely smart, we could just write everything in assembly code directly on production servers. But we aren’t, so we need accommodations.

One of these accommodations is to carry out our work serially. It’s much easier to work on exactly one thing at a time, and bring it fully to completion before starting the next task, than to juggle multiple tasks at once.

Working serially might intuitively seem slower. It feels like it would be more efficient to batch things up. But humans are so susceptible to mistakes and oversights and confusions and such, and software systems are so complicated, that batching work costs more time than it saves.

Tips for not mixing jobs

When you’re working on something, always be conscious of the answer to the question “What job am I working on right now? What exactly am I trying to accomplish?” Once you’ve determined that, ask yourself, “Is this actually just one job or are there multiple jobs inside it?” If it makes sense, separate the “what” jobs from the “how” jobs.

It may take some experimentation to find the right granularity. I wouldn’t advocate planning the entire “what” for a program (e.g. writing out the whole thing in pseudocode) before starting any of the “how”. I personally like to decide a few steps’ worth of “what”, then figure out the “how”, then switch back to “what”, and repeat.

Avoiding mixing a main job with a side job is conceptually pretty easy, although it might take some discipline. When you’re working on a job and you notice some other job that needs doing, don’t just switch focus and do that other job. Instead, make a note to do that other job. I personally like to keep a daily to-do list where I keep track of such things. Before the day ends I’ll usually either complete those “side jobs” or, if the side jobs are big enough, I’ll capture them in a way that they can be remembered for later when they can be given the level of care and attention that they call for.

Takeaways

  • Don’t mix the “what” with the “how”.
  • Don’t mix a “main job” with a “side job”.
  • It’s generally faster to carry out work serially than to try to do it in batches or in parallel.

If you want to learn testing, first learn how to program in feedback loops

Most beginner programmers (and even many experienced programmers) take a slow, painful, wasteful approach to programming.

The wasteful way

The way many programmers code is to spend a bunch of time writing code without checking to see that it works, then finally run the program once they’ve accumulated many lines of code. The program inevitably fails.

Next, the programmer sits and puzzles over what might have gone wrong. Since the programmer wrote a lot of code without checking it, there’s a lot of stuff that could possibly be the culprit, and therefore the debugging process is slow and painful.

The debugging process is usually not a systematic one but rather a guessing game. “Maybe it’s this. Nope it’s not that. Maybe it’s this other thing. Nope, it’s not that either. Hmm.” As the clock ticks, frustration mounts, and maybe a little desperation sets in. It’s not fast and it’s not fun.

The smarter way: feedback loops

Instead of working in the slow, painful, wasteful way described above, you can work in feedback loops. As I described in my other post about feedback loops, the feedback loop process goes like this:

  1. Decide what you want to accomplish
  2. Devise a manual test you can perform to see if #1 is done
  3. Perform the test from step 2
  4. Write a line of code
  5. Repeat test from step 2 until it passes
  6. Repeat from step 1 with a new goal

When you use the feedback loop method, it’s hard to run too far astray. If you only write a little bit of code at a time and you keep everything working at all times, then you’re guaranteed to always have a program that’s either fully working or very close to fully working.

Feedback loops and automated testing

Automated testing is just the practice of coding using feedback loops, but with the testing step automated.

Here’s how the feedback loop would go with automated tests involved. The automated test parts are included in bold.

  1. Decide what you want to accomplish
  2. Devise a manual test you can perform to see if #1 is done (write a test)
  3. Perform the test from step 2 (run the test)
  4. Write a line of code
  5. Repeat test from step 2 until it passes (run the test again)
  6. Repeat from step 1 with a new goal

Obviously there’s also a lot of technical knowledge that’s needed in order to write automated tests. For example, there are test frameworks that enable automated testing, there are libraries that help your tests interact with a browser, and there are libraries that help with generating test data. But more important than any particular tool are the principles behind automated testing.

Perhaps the most important idea behind automated testing is the feedback loop. And luckily for you if you’re a beginner, you can learn how to program in feedback loops without having to learn anything to do with automated testing yet. And once you do, writing automated tests will feel much more natural.

Rails can only take you so far

Applications tend to grow over time. In the beginning of an application’s life, things are typically pretty easy. Rails provides “slots” for us to put our code in. Display-related code goes in the views. Domain logic and persistence-related logic go in the models. The code that connects the two goes in the controllers.

But over time the slots fill up. Models get bloated to hundreds or even thousands of lines. Models become miscellaneous grab bags of unrelated methods. Controllers get cluttered with custom actions and grow large and hard to understand. Entropy takes over and things become a mess.

At this point, many developers sadly head down a bad path. They observe that, early on, Rails helped them with everything including code organization, and so now that they need more help, they go looking for “more Rails”. They’re run out of framework and now they want more framework. They look for tools and libraries to help them organize their code. But what’s needed isn’t tools and libraries. What’s needed are code organization skills. And tools and libraries can’t be a replacement for skills.

Frameworks are really good for certain things. They’re good for abstracting away repetitive or low-level work and giving developers leverage. Frameworks can also be good for imposing structure on a codebase, but only within narrow limits. Before long, simple heuristics (e.g. “put this type of code in models, this type of code in controllers,” etc.) cease to be enough. At that point an actual human has to take the reins from the framework and apply judgment. Non-trivial codebases are too large to be kept tidy by the simplistic heuristics that frameworks are able to provide.

If “more framework” and libraries and gems aren’t the answer, what is the answer, exactly? That’s basically the same question as “how do I write good code?” People have of course written many books attempting to help answer that question. There’s a lot to it. Learning the answer takes years.

To me, writing good code has to do with choosing clear names for variables, classes and methods; conceiving of clear and useful models and abstractions; refactoring regularly in order to keep entropy under control and keep the code understandable; and using automated tests to ensure that refactoring is possible. The best single resource I can recommend to cover these topics in a well-rounded way is Code Complete by Steve McConnell.

Regarding structure, I personally use a lot of namespaces and POROs and my Rails apps. The namespaces help me keep related things next to each other and signal to me, when I’m trying to understand a piece of code, what surrounding code is relevant and what I can ignore. Organizing my model behavior into POROs helps me avoid bloated Active Record models. When I notice that an Active Record model is starting to lose cohesion, I’ll often look in my model for “missing abstractions” and split those missing abstractions off into new POROs. The resulting structure has very little to do with Rails. I’m mainly just leaning on the principles of object-oriented programming.

I think Rails is the greatest web framework ever created. The things Rails helps with, it does a fantastic job of helping with, including helping to organize code during the early stages of an application’s life. But once your application grows beyond a certain size, Rails can no longer help you much and you’re on your own to decide how to keep your code organized.

How and why to use Ruby’s method_missing

Why method_missing exists

Normally, an object only responds to messages that match the names of the object’s methods and public accessors. For example, if I send the message first_name to an instance of User, then the User object will only respond to my message of first_name if User has a method or accessor called first_name.

But sometimes it’s useful to allow objects to respond to messages that don’t correspond to methods or accessors.

For example, let’s say we want to connect our User object to a database table. It would be very convenient if we could send messages to instances of User that correspond to the database table’s column names without having to either explicitly define new methods or do something inelegant like, for example, user.value(:first_name). It would be better if we could get the database value by calling user.first_name.

What’s more, if we added a new column called last_name, it would be good if we could just do user.last_name without having to change any code.

method_missing allows us to do things like this. In this post we’ll see how by going through an example that’s similar to (but simpler than) the database example above.

Arbitrary attribute setter example

In the below example, we’ll use method_missing to define some behavior that allows us to arbitrarily set values on an object. We’ll have an object called user on which we can call set_first_name, set_last_name, set_height_in_millimeters or whatever other arbitrary values we want.

A plain User object

In the following snippet, we define a class called User which is completely empty. We attempt to call set_first_name on a User instance which, of course, fails because User has no method called set_first_name.

# user.rb

class User
end

user = User.new
user.set_first_name("Jason")

When we run the above, we get undefined method `set_first_name' for #<User:0x00000001520e01c0> (NoMethodError).

$ ruby user.rb
Traceback (most recent call last):
user.rb:9:in `<main>': undefined method `set_first_name' for #<User:0x00000001520e01c0> (NoMethodError)

Adding method_missing

Now we add a method to the User class with a special name: method_missing.

In order for our method_missing implementation to work it has to follow a certain function signature. The first parameter, method_name, corresponds to the name of the message that was passed (e.g. first_name). The second parameter, *args corresponds to any arguments that were passed, and comes through as an array, thanks to the splat operator.

In this snippet all we’ll do is output the values of method_name and *args so we can begin to get a feel for how method_missing works.

class User
  def method_missing(method_name, *args)
    puts method_name
    puts args
  end
end

user = User.new
user.set_first_name("Jason")

When we run this we see set_first_name as the value for method_name and Jason as the value for args.

$ ruby user.rb
set_first_name
Jason

Parsing the attribute name

Now let’s parse the attribute name. When we pass set_first_name, for example, we want to parse the attribute name of first_name. This can be done by grabbing a substring that excludes the first four characters of the method name.

class User
  def method_missing(method_name, value)
    attr_name = method_name.to_s[4..]
    puts attr_name
  end
end

user = User.new
user.set_first_name("Jason")

This indeed gives us just first_name.

$ ruby user.rb 
first_name

Setting the attribute

Now let’s set the actual attribute. Remember that args will come through as an array. (The reason that args is an array is because whatever method is called might be passed multiple arguments, not just one argument like we’re doing in this example.) We’re interested only in the first element of args because user.set_first_name("Jason") only passes one argument.

class User
  def method_missing(method_name, *args)
    attr_name = method_name.to_s[4..]
    instance_variable_set("@#{attr_name}", args[0])
  end
end

user = User.new
user.set_first_name("Jason")
puts user.instance_variable_get("@first_name")

When we run this it gives us the value we passed it, Jason.

$ ruby user.rb
Jason

We can also set and get any other attributes we want.

class User
  def method_missing(method_name, *args)
    attr_name = method_name.to_s[4..]
    instance_variable_set("@#{attr_name}", args[0])
  end
end

user = User.new
user.set_first_name("Jason")
user.set_last_name("Swett")
puts user.instance_variable_get("@first_name")
puts user.instance_variable_get("@last_name")

When we run this we can see that both values have been set.

$ ruby user.rb
Jason
Swett

A note about blocks

In other examples you may see the method signature of method_missing shown like this:

def method_missing(method_name, *args, &block)

method_missing can take a block as an argument, but actually, so can any Ruby method. I chose not to cover blocks in this post because the way method_missing‘s blocks work is the same as the way blocks work in any other method, and a block example might confuse beginners. If you’d like to understand blocks more in-depth, I’d recommend my other post about blocks.

Takeaways

  • method_missing can be useful for constructing DSLs.
  • method_missing can be added to any object to endow that object with special behavior when the object gets sent a message for which it doesn’t have a method defined.
  • method_missing takes the name of the method that was called, an arbitrary number of arguments, and (optionally) a block.

“Am I smart enough for programming?”

Sometimes on programming forums someone asks a question along the lines of, “Am I smart enough for programming?”

The bad answer

Typically the answers to this question are some variation of “Yes! Keep going! You can do it!”

These answers are well-intentioned but not honest. The answerer has no idea who the asker is or how smart they are. That’s like someone asking, “Am I tall enough to ride a rollercoaster?” and people answering, “Yes! You can do it! Just believe in yourself!” It’s not necessarily true, obviously. And it’s not helpful.

The good answer

When someone asks if they’re smart enough for programming, what they’re probably experiencing is that they’re spending a lot of time and effort trying to learn programming and not feeling like their progress is proportionate to their expenditure. They figure that a likely explanation for their difficulty is that they’re not smart enough.

These people are probably reasoning that you have to have some minimum level of smartness in order to be able to learn programming, and that not everyone meets that minimum level of smartness, and if they aren’t smart enough then they shouldn’t waste their time on trying to achieve the unachievable.

For anyone wondering asking this question of themselves, I would point out that “Am I smart enough for programming?” is a really vague question. It’s like asking, “Am I tall enough for basketball?” Being tall obviously helps in basketball but it’s not like there’s a hard line at six feet and you should just give up if you’re less than six feet tall.

If you want to be a programmer, being smart helps, just like being tall helps you in basketball. But it’s not the only factor. It’s not as though the minimum IQ for programming is 120 and if your IQ is below 120 you should give up.

Everyone who wants to become a competent programmer has to learn a million little details. It’s easy to mistake “I’m having a hard time learning all these details” for “I’m not smart enough to grasp the material”. When I first started learning to speak Chinese, I was amazed at how inept I was and how long it took me to learn anything. When I would try to speak Chinese with my Chinese friend who was helping me, I would understand such a comically tiny amount of what she said that the whole exercise felt like a waste of time to me. But the reason for the difficulty wasn’t because I’m stupid, it’s just that Chinese is very different from English and there’s a lot of stuff to learn.

I’ll add yet one more point. You don’t actually have to be that smart to be a successful programmer. I’ve met plenty of programmers who are pretty dull and can’t code their way out of a paper bag. Yet, somehow, they maintain programming jobs. (Some of these dummies have even been my past bosses!) So if you’re worried that you don’t meet the minimum, you might not need to worry. The bar isn’t that high.

Takeaways

  • Asking if you’re smart enough for programming is like asking if you’re tall enough for basketball. It’s not really a question that makes sense.
  • No one is going to tell you that you’re too dumb for programming and you should quit. There’s no test you can take to find out if you’re smart enough. It’s just not really knowable.
  • Learning programming involves learning a million little details. Don’t confuse the the labor of learning with a lack of intelligence.
  • The minimum necessary level of intelligence in order to become a successful programmer is actually pretty low.