2012
RubyConf 2012 notes
My notes, in a somewhat sketch-esque fashion, from RubyConf 2012. I hope they’re useful and/or amusing to you!
[gallery link=“file”]
Marginal pennies and dollars
The give a penny, take a penny jar is a logical conundrum. It is not, on its surface, a rational thing. I have no data, but I suspect very few people who put money into them are doing so because they plan on taking money out later. A bank is different from a give/take a penny jar.
Personally, I put money in because I can and because I fancy myself not a jerk. The latter is what makes more rational sense. I put money in because it’s utility to me is marginal, but the utility of feeling better about myself is non-marginal.
In My Blue Heaven, Steve Martin plays a semi-reformed mobster in the witness protection program. He starts compiling a book of his truisms for living life. One is “it’s not so much tipping I believe in as over-tipping.” His character does this partially because he’s a little flashy, and partially, I think, because he has to be a likable protagonist.
I’d like to be a likable protagonist too, but I like over-tipping whenever possible for another reason. Pretty much anyone who works for tips is working really hard for every dollar they make. An extra dollar here or there is trivial to someone with a desk-job like myself, but less trivial to tip-earners who are technically paid less than minimum wage. An extra five or ten percent on a single tip won’t change their life, but it probably doesn’t hurt either.
I like making people’s day better with laughs and smiles, but I’m not above buying a tiny fraction of a better day for someone else. Marginal pennies and dollars add up.
Ruthlessness and fighting for it
Being ruthless to yourself means every time you say “oh, I’ll just open up this internal bit over here…” use that moment to give yourself whatever negative feedback you need to go back and write the correct interface. Imagine the bugs you’ll get later. Give yourself a 12 volt shock through your chair. Picture the sleepless nights chasing down an issue that only happens for that one guy but it’s the guy who signs your paycheck.
I dropped this in my drafts folder months ago and came back to it today. It’s still something I need to hear. Get it working, and then ruthlessly edit and refactor until it’s code that won’t cause you to cringe when others bring it up.
In improv and code, I’ve recently come across the notion that there are things we need to fight for. Fight, not in the sense of conflict, but in the sense that there is an easy or natural way to do something, and then there is the way that maintains our sense of pride and quality. Not necessarily the “right” or high-minded way to do something, but the way that does not leave us feeling compromised as a creative person.
Your homework is to write down the qualities important to you, the ones that make you proud of your work and happy to share it. Then work from this checklist every day:
- Write the code, rehearse the scene, play the song, etc.
<li>Decide whether it expresses your qualities.</li>
<li>If it does, ship it. If it doesn't, edit ruthlessly until it does.</li>
Rinse/repeat/tell everyone about it.
Working with Ruby's GVL
Visualising the Ruby Global VM Lock. A nice commit-by-commit look at how extensions for Ruby 1.9 work with the GVL, what that looks like as tests run, and how to release the GVL to allow for better parallelism.
A pithy take on development vs. operations
The essential, face-palming difference between too many development teams and too many operations teams is thus:
Development: “I know how it works, but I don’t know how to make it work”
Operations: “I know how to make it work, but I don’t know how it works”
Be the solution, friends.
How Ruby IO is formed
Ruby's IO Buffering And You! Jesse Storimer screencasts his way through what happens when you read and write to files and sockets in Ruby, explaining the behavior and spelunking through Rubinius' implementation of IO. You'll learn stuff. If you want to learn even more stuff, check out Jesse's new book Working with TCP Sockets. Jesse is fantastic at describing Unixy things concisely; you'll like it.
Follow the smells
It’s handy to know a lot about programming langauges, patterns, “best” practices, and anecdotal experience in applying those ideas. But premature application of ideas can lead to its own bad experiences and new anecdotal evidence. How can you apply ideas without falling into the premature architecture/optimization trap?
Follow the smells.
You know what a bad method looks like. You know what slow code looks like and how to find it. You can tell when a class is doing too many things. Follow those smells.
Once you’ve smelled it, you’ve dealt it. Kidding!
Once you’ve found a culprit that is imminently making your life as a developer harder, you have to explain it to someone else. Tell them why it’s slow, badly coupled, or too much architecture. Once they’re convinced, then you can reach into that deep knowledge of languages, patterns, practices, and experience to fix the problem.
You commit the code, push it to production, and do a happy dance. You’re not done.
Now you need to share it. Write down what you did and share it with your team, lest the problem happen again. Show your co-workers how to find the problem elsewhere and quickly dispatch it.
If you do it this way, you’re helping yourself three times:
- You fixed the original problem
- You showed your colleagues how to fix the problem or how to avoid creating the problem
- You showed your colleagues how to make measurable, if minor, progress in making your application better without the need for drastic projects
Follow the smells. Fix the smell. Put the fix in everyone else’s brain. Repeat.
A handful of useful project mantras
You could do a lot worse than following the heuristics set out by this Software Architecture cheat sheet. The tip I need to follow more often is "Is There Another Way"; I frequently get way too caught up in my first idea, which is usually too simplistic or requires too much architecture. The tip I often try to guide people towards is "What If I Didn't Have This Problem?"; routing around problems or trying to reduce them to problems that require less code is a super-powerful judo chop.
A kingdom of concerns
When doing object-oriented programming and following SOLID principles, there is sometimes a concern that classes will proliferate and you end up with a Kingdom of Nouns.
I find it more concerning when there is a proliferation of concepts. Too many layers, too many patterns, too many frameworks. The best designs I’ve seen provide just one or two concepts that manage to tie everything together without breaking orthogonality.
The great thing about Rails and Sinatra was that they boiled previously conceptually heavy domains down to a simpler, better set of ideas. In doing so, they move you up the hierarchy of needs, so to speak, and you still end up inventing your own concepts and nouns. More on that soon!
bitly's nsq has some good ideas
NSQ is a realtime message processing system designed to operate at bitly's scale, handling billions of messages per day.It promotes distributed and decentralized topologies without single points of failure, enabling fault tolerance and high availability coupled with a reliable message delivery guarantee.
No SPOFs and reliable message delivery, without relying on something like ZooKeeper, is a big claim. They have some novel approaches to these problems.
First, they run an intermediary daemon, nsqlookupd
, between the producers/consumers and the actual queues. These daemons monitor all the available queue servers and tell the clients what to connect to. No configuration of actual queue servers is known to applications. They then run multiple lookup daemons, which are stateless and don’t need to agree with each other in order for the system to operate properly.
Reliable message delivery is provided with at-least-once message delivery semantics. They require all consumers to de-duplicate messages or restrict their operations to idempotent operations. Not exactly legacy friendly, as many applications are coded with the assumption of a closed, one-shot world. But. Idempotence: I highly recommend it if you have the means.
If you need to prevent losing messages due to the FBI stealing your servers, which is something you definitely need to account for, you can set up redundant pairs of servers and rely on deduplication/idempotence to make sure you’re only processing messages once, even if you consume them multiple times.
In summary: lots of good ideas here. Perhaps some of them could be applied to how people are using Resque?
I got Clojure stacks
Here’s a Sunday afternoon hack. It’s a “stack” machine implemented in Clojure. I intended for it to be a stack machine, no airquotes, but I got it working and realized what I’d really built was a machine with two registers and instructions that treat those two registers as a stack. Pretty weird, but it’s not bad for a weekend hack.
I’m going to break my little machine down, and highlight things that will feel refreshingly different to someone, like me, who has spent the past several years in object-oriented languages like Ruby. What follows is observations; I’m still very new to Clojure, despite familiarity with the concepts, so I’ll pass on making global judgements.
Data structures as programs as data
I’ve seen more than one Rubyist, myself included, say that code-as-data, a concept borrowed from Lisp’s syntax, is possible and regularly practiced in Ruby. DSLs and class-oriented little languages accomplish this, to some degree. In my experience, this metaprogramming is really happening at the class level, using the class to hold data that dynamic code parses to generate new behaviors.
In contrast, Clojure, being a Lisp, programs really are data. To wit, this is the crux of my stack machine; the actual stack machine program is a Clojure data structure that in turn specifies some Clojure functions to execute:
(def program
[['mpush 1]
['mpush 2]
['madd]
['mpush 4]
['msub]
['mhalt]])
(run program)
If you’ve never looked at Clojure or Lisp code, just squint and I bet you’ll keep up. This snippet defines a global variable, of sorts, program
, whose value is a list of lists (think Arrays) specifying the instructions in my stack machine program. In short, this program pushes two values on the stack, 1 and 2, adds them, pushes another value 4, subtracts 4 from the result of the addition, and then halts, which prints out the current state of the “stack” registers.
I’ve got a function named run
which takes all these instructions, does some Clojure things, then hands them off to instruction functions for execution.
Some familiar idioms
Let’s look at run
. It’s really simple.
(defn run [instructions]
(reduce execute initial-state instructions))
This function takes one argument, instructions
, a Clojure collection (generally called a seq
; this one in particular is a vector
). Clojure has an amazing library of functions that operate on collections, just as Ruby has Enumerable
. In fact, reduce
in Clojure is the same idea as inject
in Ruby (reduce
is aliased to inject
in Ruby!). The way I’m calling it says “iterate over a collection instructions
, calling execute
on each item; on the first iteration, use initial-state
as the initial value of the accumulated collection”.
initial-state
is another global variable whose value is a mapping (in Ruby, a hash) that maintains the state of the machine. It has two keys, op-a
and op-b
, representing my two stack-ish registers.
(def initial-state
{:op-a nil :op-b nil})
Now you’d expect to find an execute
function that takes a collection plus a value and generates a new version of the collection, just like Ruby’s inject
. And here that function is:
(defn execute [state inst]
(let [fun (ns-resolve *ns* (first inst))
params (rest inst)]
(apply fun [params state])))
This one might require extra squinting for eyes new to Clojure. execute
takes two arguments, the current state of the stack machine, state
, and the instruction to execute, inst
. It then uses let
to create local variables based on the values of function’s parameters. I use Clojure’s mechanism for turning a quoted variable name (quoting, in Lisp, means escaping a variable name so the interpreter doesn’t try to evaluate it) into a function reference. Because the instruction is of the form [instruction-name arg arg arg ...]
, I use first
and rest
to split the instruction into the function name, bound to fun
and argument list, bound to params
.
The meat of the function “applies” the function I extracted in the let block to the arguments I extracted out of the instruction. Think of apply
like send
in Ruby; it’s a way to call a function when you have a reference to it.
The sharp reader would now start searching for a bunch of functions, each of which implements an instruction for our stack machine. And so…
Some boilerplate arrives
Here is the implementation for mpush
, madd
, and mhalt
:
(defn mpush [params state]
(let [a (state :op-a)
b (state :op-b)
v (first params)]
{:op-a v :op-b a}))
(defn madd [params state]
(let [a (state :op-a)
b (state :op-b)]
{:op-a (+ a b) :op-b nil}))
(defn mhalt [params state]
(println state))
Each instruction takes some arguments and the state of the machine. They do some work and return a new state of the stack machine. Easy, and oh-so-typically functional!
These instructions are where I’d introduce something clever-ish in Ruby. That let
where the register values are extracted feels really boilerplate-y. In Ruby, I know what I would do about that: a method taking a block, probably.
I’m not sure how I’d clean this up in Clojure. A macro, a function abstraction? I leave it as an exercise to the reader, and to myself, to find something that involves less copypasta each time a new instruction is implemented.
I found some pleasant surprises in this foray into Clojure:
- Building programs from bottom-up functions in a functional language is at least as satisfying as doing the same with a TDD loop in an object-oriented language. It is just a conducive to dividing a problem into quickly solved blocks and then putting the whole thing together. It does, however, lack a repeatable verification artifact as a secondary output.
- At first I was a little skeptical of the fact that Clojure mappings (hashes) can be treated as data structures, by passing them to functions, or as functions, by calling them using a key to extract as the parameter. In practice, this is a really awesome thing and it’s a nice way to write one’s own abstractions as well. There’s something to using higher-order functions more prevalently than Ruby does.
- The JVM startup isn’t quick in absolute terms, but at this point it’s faster than almost any Rails app, and many pure Ruby apps, to boot. Damning praise for the JVM and Ruby, but the take-away is I never felt distracted our out-of-flow due to waiting around on the JVM.
Bottom line: there’s a lot to like in Clojure. It’s likely you’ll read about more forays into Clojure in this space.
Faster, computer program, kill kill!
Making code faster requires insight into the particulars of how computers work. Processor instructions, hardware behavior, data structures, concurrency; it’s a lot of black art. Here’s a few things to read on the forbidden lore of fast programs:
Fast interpreters are made of machine sympathy. Implementing Fast Interpreters. What makes the Lua interpreter, and some JavaScript interpreters, so quick. Includes assembly and machine code details. Juicy!
Lockless data structures, the easy way. A Java lock-free data structures deep dive. How do those fancy java concurrent libraries work? Fancy processor instructions! Great deep dive.
Now is an interesting time to be a bottleneck. Your bottleneck is dead. Hardware, particularly IO, is advancing such that bottlenecks in code are exposed. If you’re running on physical hardware, especially if you have solid-state disks, your bottleneck is probably language-bound or CPU-bound code.
Go forth, read a lot, measure twice (beware the red herrings!), and make faster programs!
When to Sinatra, when to Rails
On Rails, Sinatra, and picking the right tool for the job. Pedro Belo, of Heroku fame, finds Rails is way better for pure-web apps and Sinatra is way better for pure-API apps. Most of it comes down to Rails has better tooling and Sinatra is better for scratching itches, which happens a lot more in APIs than applications. I’m not ready to pronounce this the final word, but what he’s saying lines up with much of my experience.
That said, you can get pretty far with a Rails API by segregating it from your application. That is, your app controllers inherit from ApplicationController
and your API controllers inherit from ApiController
. This keeps the often wildly different needs of applications and APIs nice and distinct.
Common sense code checks
Etsy’s Static Analysis for PHP. This isn’t as complicated as you might think. While Facebook’s HipHop is used, and is quite sophisticated, a lot of this is just common sense. Trigger code reviews when oft-misused functions are used or when functions that involve security things are introduced.
This stuff is great for an intern or new team member to get a quick win with. So next time you bring someone onto your team, why not turn them loose on these kinds of quick, big wins?
Designing for Concurrency
A lot is made about how difficult it is to write multi-threaded programs. No doubt, it is harder than writing a CRUD application or your own testing library. On the other hand, it’s not as difficult as writing a database or 3D graphics engine. The point is, it’s worth learning how to do. Skipping the hubris and knowing your program will have bugs that require discipline to track down is an enabling step to learning to write multithreaded programs.
I haven’t seen much written about the experience of writing a concurrent program and how one designs classes and programs with the rules of concurrency in mind. So let’s look at what I’ve learned about designing threaded programs so far.
The headline is this: only allow objects in consistent states and don’t rely on changing state unless you have to. Let’s first look at a class that does not embody those principles at all.
class Rectangle
attr_accessor :width, :height
def orientation
if width > height
WIDE
else
TALL
end
end
WIDE = "WIDE".freeze
TALL = "TALL".freeze
end
Just for fun, mentally review that code. What are the shortcomings, what could go wrong, what would you advise the writer to change?
For our purposes, the first flaw is that new Rectangle
objects are in an inconsistent state. If we create an object and immediately call orientation
, bad things will happen. If you’re typing along at home:
begin
r = Rectangle.new
puts r.orientation
rescue
puts "whoops, inconsistent"
end
The second flaw is that our object allows bad data. We should not be able to do this:
r.width = 100
r.height = -20
puts r.orientation
Alas, we can. The third flaw is that we could accidentally share this object across threads and end up messing up the state in one threads because of logic in another thread. This sort of bug is really difficult to figure out, so designing our objects so it can’t happen is highly desirable. We want to make this sort of code safe:
r.height = 150
puts r.orientation
When we modify width
or height
on a rectangle, we should get back an entirely new object.
Let’s go about fixing each of these flaws.
Encapsulate object state with Tell, Don’t Ask
The first flaw in our Rectangle
class is that it isn’t guaranteed to exist in a consistent state. We go through contortions to make sure our databases are consistent; we should do the same with our Ruby objects too. When an object is created, it should be ready to go. It should not be possible to create a new object that is inconsistent.
Further, we can solve the second flaw by enforcing constraints on our objects. We use the “Tell, Don’t Ask” principle to ensure that when users of Rectangle
change the object’s state, they don’t get direct access to the object’s state. Instead, they must pass through guards that protect our object’s state.
All of that sounds fancy, but it really couldn’t be simpler. You’re probably already writing your Ruby classes this way:
class Rectangle
attr_reader :width, :height
def initialize(width, height)
@width, @height = width, height
end
def width=(w)
raise "Negative dimensions are invalid" if w < 0
@width = w
end
def height=(h)
raise "Negative dimensions are invalid" if h < 0
@height = h
end
def orientation
if width > height
WIDE
else
TALL
end
end
end
A lot of little things have changed in this class:
- The constructor now requires the width and height arguments. If you don’t know the width and height, you can’t create a valid rectangle, so why let anyone get confused and create a rectangle that doesn’t work? Our constructor now encodes and enforces this requirement.
- The
width=
andheight=
setters now enforce validation on the new values. If the constraints aren’t met, a rather blunt exception is raised. If everything is fine, the setters work just like they did in the old class. - Because we’ve written our own setters, we use
attr_reader
instead ofattr_accessor
.
With just a bit of code, a little explicitness here and there, we’ve now got a Rectangle
whose failure potential is far smaller than the naive version. This is simply good design. Why wouldn’t you want a class that is designed not to silently blow up in your face?
The crux of the biscuit for this article is that now we have an object with a narrower interface and an explicit interface. If we need to introduce a concurrency mechanism like locking or serialization (i.e. serial execution), we have some straight-forward places to do so. An explicit interface, specific messages an object responds to, opens up a world of good design consequences!
Lean towards immutability and value objects whenever possible
The third flaw in the naive Rectangle
class is that it could accidentally be shared across threads, with possibly hard to detect consequences. We can get around that using a technique borrowed from Clojure and Erlang: immutable objects.
class Rectangle
attr_reader :width, :height
def initialize(width, height)
validate_width(width)
validate_height(height)
@width, @height = width, height
end
def validate_width(w)
raise "Negative dimensions are invalid" if w < 0
end
def validate_height(h)
raise "Negative dimensions are invalid" if h < 0
end
def set_width(w)
self.class.new(w, height)
end
def set_height(h)
self.class.new(width, h)
end
def orientation
if width > height
WIDE
else
TALL
end
end
end
This version of Rectangle
further extracts the validation logic into separate methods so we can call it from the constructor and from the setters. But, look more closely at the setters. They do something you don’t often see in Ruby code. Instead of changing self
, these setters create an entirely new Rectangle
instance with new dimensions.
The upside to this is, if you accidentally share an object across threads, any changes to the object will result in a new object owned by the thread that initiated the change. This means you don’t have to worry about locking around these Rectangle
s; in practice, sharing is, at worst, copying.
The downside to this side is you could end up with a proliferation of Rectangle
objects in memory. This puts pressure on the Ruby GC, which might cause operational headaches further down the line. Clojure gets around this by using persistent data structures that are able to safely share their internal structures, reducing memory requirements. Hamster is one attempt at bringing such “persistent” data structures to Ruby.
Let’s think about object design some more. If you’ve read up on domain-driven design, you probably recognize that Rectangle
is a value object. It doesn’t represent any particular rectangle. It binds a little bit of behavior to a domain concept our program uses.
That wasn’t so hard, now was it
I keep trying to tell people that, in some ways, writing multithreaded program is as simple as applying common object-oriented design principles. Build objects that are always in a sensible state, don’t allow twiddling that state without going through the object’s interface, use value objects when possible, and consider using immutable value objects if you’re starting from scratch.
Following these principles drastically reduces the number of states you have to think about and thus makes it easier to reason about how the program will run with multiple threads and how to protect data with whatever form of lock is appropriate.
Cardinal sins
It is conceivable that a really good machine can learn our hash algorithm really well, but in the case of string hashing we still have to walk some memory to give us reasonable assurance of unique hash codes. So there's performance sin #1 violated: never read from memory.Avoiding Hash Lookups in a Ruby Implementation, on the quest to eliminate the use of ad-hoc hashes inside JRuby. I love that the cardinal sin of a runtime is to avoid memory reads. It makes avoiding random database lookups in web applications look like a walk in the park.
On the other hand, consider how much fun it is to write compilers; their cardinal sin is to avoid conditionals or anything that would stall the processor pipeline. If that seems pedestrian, then consider the cardinal sin of a processor designer: don’t do anything that will take longer than one clock cycle, or half a billionth of a second if you’re keeping score at home.