Turns out Ruby is great for scripting!

Earlier last year, I gave myself two challenges:

  • write automation scripts in Ruby (instead of giving up on writing them in shell)
  • use system debugging tools (strace, lsof, gdb, etc.) more often to figure out why programs are behaving some way

Of course, I was almost immediately stymied on the second one:

sudo dtruss -t write ruby -e "puts 'hi!'"

dtrace: failed to execute ruby: dtrace cannot control executables signed with restricted entitlements

dtruss is the dtrace-powered macOS-equivalent of strace. It is very cool when it works. But. It turns out Apple has a thing that protects users from code injection hijinks, which makes dtrace not work. You can turn it off but that requires hijinks of its own.

I did end up troubleshooting some production problems via strace and lsof. That was fun, very educational, and slightly helpful. Would do again.

I did not end up using gdb to poke inside any Ruby programs. On the whole, this is probably for the better.

I was more successful in using Ruby as a gasp scripting language. I gave myself some principles for writing Ruby automation:

  • only use core/standard library; no gem requires, no bundles, etc.
  • thus, shell out to programs likely to be available, e.g. curl
  • if a script starts to get involved, add subcommands
  • don’t worry about Ruby’s (weird-to-me) flags for emulating sed and awk; stick to the IRB-friendly stuff I’m used to

These were good principles.

At first I tried writing Ruby scripts as command suites via sub. sub is a really cool idea, very easy to start with, makes discovery of functionality easy for others, and Just Works. You should try it some time!

That said, often I didn’t need anything fancy. Just run a few commands. Sometimes I even wrote those with bash!

But if I needed to do something less straightforward, I used template like this:

#!/usr/bin/env ruby

test # Run the test suite
test ci # Run test with CI options enabled
test acceptance # Run acceptance tests (grab a coffee....)

module Test

def test
`rspec spec`

# ... more methods for each subcommand


if __FILE == $0
cmd = ARGV.first

case cmd
when "ci"
# ... a block for each subcommand

This was a good, friction-eliminating starting skeleton.

The template I settled on eliminated the friction of starting something new. I’d write down the subcommands or workflow I imagined I needed and get started. I wrote several scripts, delete or consolidated a few of them after a while, and still use a few of them daily.

If you’re using a “scripting” language to build apps and have never tried using it to “script” things I “recommend” you try it!

Tinkers are a quantity game, not a quality game

I spend too much time fretting about what to build my side projects and tinkers with. On the one hand, that’s because side projects and tinkers are precisely for playing with things I normally wouldn’t get a chance to use. On the other hand, it’s often dumb because the tinker isn’t about learning a new technology or language.

It’s about learning. And making stuff. Obsessing over the qualities of the build materials is besides the point. It’s not a Quality game, it’s a Quantity game.

Now if you’ll excuse me I need to officiate a nerd horserace between Rust, Elm, and Elixir.

The least bad solution

Sometimes I look over the options and constraints to choose something suboptimal. I have to pick the least-bad solution.

I recently chose a least-bad way to write a test. In a Rails app, the most sensible thing to solve my problem was something like this:

def propagate_from_child_to_parent

In the test, I ended up having to write this assertion

expect_any_instance_of(ModelParents).to receive(:do_a_sideeffect)

This kind of stub and assertion is clearly a smell. But, consider the alternatives:

  • stub out the child model object under so that find_each returns a stub object that I can make sure do_a_sideffect is called on
  • try to hack around ActiveRecords associations so it returns the same object as I inject in my test
  • seek out some other result of do_a_sideeffect that I could assert on

In the end, it felt like the shady mock+assertion was the best choice. Using that particular assertion says “slow down and pay attention, a special thing is happening here”. It’s not something I want to do every time, but it was the least bad solution in this context.

Wanted: state machines in the language

Our programming languages are often structured around the problem domain of compilers and the archaic (for most of us) task of converting things people understand to a thing the computer can execute.

Why don’t our languages have deeper support for the ways we reason about problem domains or the ways we struggle to reason. For example, why aren’t state machines and checking their sanity (or marking their unsoundness) a thing in pretty much any language?

The unhelpful answer is “because you can write a state machine in library code”. Which leads me to ask, why don’t we have popular state machine clones? Why is there no xUnit or Sinatra of state machines that is widely cloned to fresh and exciting languages?

The cynical answer is “because many programmers don’t want to think that hard”. The optimistic answer is that there’s room for someone to capture this problem space as well as xUnit did for programmer testing or Sinatra did for turning URL-like strings into method calls. You could be famous!

We’re all adults here, but we’re not all mind readers

My favorite advice on the topic of method visibility (i.e. public vs. private) comes from Python creator Guido van Rossum. It goes something like “we’re all adults here” and says it’s not really a necessary thing for compilers/runtimes to hide methods from specific callers. Don’t go mucking around in other object’s implementations. I still think that’s mostly right.

Except, coming up to speed on a new code base is vastly easier when there’s some delineation of the waterline between an object’s public API which it expects other objects to use and its private implementation which it does not. It tells me a) the private methods are open for change and refactoring and b) below the private “waterline”, don’t bother going any deeper when spelunking to figure out how this program works.

In a new or strange codebase, every little bit of tractability helps.

Here comes GraphQL

GraphQL is gaining purchase outside of the JavaScript communities and this seems like a pretty good thing. Shopify and GitHub have jumped on board. Absinthe (Elixir) and graphql-ruby have caught my attention, though I haven’t had an opportunity to tinker with them yet.

That said, I like that GraphQL (and JSON API) let service developers focus on exposing a specific data model and optimizing access to it rather than taking a side quest through REST API design. For application developers, building screens and interactions with the data they need defined inline seems like a big win for contextual understanding.

As ever, the risk of using any kind of mapping layer, whether its objects and relational data or JSON object graphs to downstream service calls, is creating a naive, one to one mapping that create awkwardness and inefficiency.

Refactor the cow paths

Ron Jeffries, Refactoring — Not on the backlog!

Simples! We take the next feature that we are asked to build, and instead of detouring around all the weeds and bushes, we take the time to clear a path through some of them. Maybe we detour around others. We improve the code where we work, and ignore the code where we don't have to work. We get a nice clean path for some of our work. Odds are, we'll visit this place again: that's how software development works.

Check out his drawings, telling the story of a project evolving from a clear lawn to one overwhelmed with brush. Once your project is overwhelmed with code slowing you down, don’t burn it down. Jeffries says we should instead use whatever work is next to do enabling refactorings to make the project work happens. Since locality is such a strong force in software, it’s likely that refactoring will help the next bit of project work. Repeat several times and a new golden path emerges through your software.

In other words, don’t reach for a new master plan when the effort to change your software goes up. Pave the cow paths through whatever work you’re doing!

A few qualities of mature developers

What is technical leadership? Per Mature Developers, it’s a lot of things. My favorites:

So one of the first and most important qualities of mature developers is they’re more often than not paying attention to what is going on around them. They’re deliberately taking their time to observe before proceeding (put succinctly as STOP; Stop, Take a breath, Observe, Proceed).

It is so hard for me to do the stop and breath part.

Sharing the [technical] vision with other involved parties not only serves as a perfect opportunity for practicing one’s skills to explain deeply technical terms and circumstances with non-technical people. It also serves the purpose to validate the vision in terms of relevance to business value and other aspects.

Assessing and understanding risks better puts them into a position where it’s also more likely they’ll actually take risks. Risks which, without the knowledge about business value and the bigger context, may look too big to be worthwhile. But not for mature developers who are able to see beyond the obvious risks and include more aspects into their judgement.

Managing risk, but not overmanaging it: also very difficult.

Previously: Thoughts on “Being a Senior Engineer”.

A few folks suggested I try lazy enumerables to make my extremely chained style practical. I was curious about the actual costs of my style, so it’s time for lies and microbenchmarks! Turns out naively chaining a bunch of maps together isn’t very costly, so go with that to start.

Lazy came in much slower than consolidating the logic in one loop or chaining them without lazy. I thought, I must not have used lazy properly. Turns out, I’m probably showing that laziness isn’t well suited to iterating over collections without an early termination clause (e.g. a take, first, or find) and that for small collections (like an 87-line /etc/passwd), the cost of the lazy plumbing can noticeably outweigh the work done inside the loops. Thanks to Rein Heinrich for talking me to the bottom line!