“Yes, mom. I go there and I code with them. Yes, all day. That’s how it works.”
My mother didn’t completely understand my enthusiasm to spend a day in harness at Pivotal Labs, but she seemed willing to accept my reasoning: namely, that pair programming for that much time with more senior developers to myself would allow me to learn a whole lot of stuff. And it did, in everything from Java to TDD to software design. Here are a few of the choicest lessons I got to learn as a pretend-pivot-for-the-day.
Statically vs. Dynamically Typed Languages
So people say that Java [as a statically typed language] is more verbose. Okay, but how much of it did you actually have to type?
My programming network, so far, comprises mostly Rubyists: as such, the conversations I’ve heard about static vs. dynamic typing favor the latter, which is what Ruby does. The argument I hear the most often for a dynamically typed language applauds the high speed of iteration that they facilitate. That said, the debate rages online, and some of the better-thought-out answers emphasize that advantage less heavily. At any rate, working with the pivots gave me an opportunity to see the debate from the perspective of a programmer using a statically-typed language on an app. As it turns out, static typing offers opportunities to use tools that…well, sand away at some of the cons of static typing.
The entire day at Pivotal we worked in an IDE called IntelliJ. Until today, the only honest-to-goodness IDE I had used (jGRASP hardly counts) was Eclipse. Mind you, the first time I got stuck using Eclipse, I actually tried to escape to jGRASP: I’d rather be left alone to program badly than be interrupted every five seconds with a woefully inarticulate “we think this is probably wrong” message. Compared to jGRASP, I was impressed with Sublime’s functionality when I started using that for Ruby. I felt skeptical of IntelliJ because of the degree to which people compare it to Eclipse (even favorably), but the rumors, it turns out, are true: IntelliJ’s far more articulate messages do a much better job of guiding programmers in the event of poorly declared or out-of-scope variables. The thing also takes advantage of Java’s statically typed nature, which provides enough information to the IDE to allow it to automatically generate things like method stubs, getters, setters, and constructors. Upon letting IntelliJ generate every line of code in a class we were using for testing, one of my pairs commented “So people say that Java is more verbose. Okay, but how much of it did you actually have to type?”
But perhaps the larger point here is a slippery truth that the programming community at large sometimes fails to grasp: there’s very little point in fielding a discussion about which is “better” between statically and dynamically typed languages, or between two languages, or between functional and OO, or between any of the things that you’re sure to find somebody bickering about on StackOverflow. The more mature and detached among the tech people will argue that it’s a matter of picking the right tool for a job, rather than the best tool overall. I’d support that line of reasoning, and I’d also offer a corollary: the tools built on top of and in conjunction with other tools can, and should, change our judgments about which languages and styles are right for which jobs. In this case, IntelliJ happens to make Java a pleasure to write by eliminating some of the time spent writing boilerplate that so many people hate about it while preserving, and perhaps enhancing, the advantages of Java and its statically-typed-ness. That’s not separate from Java: the two in conjunction can be “fun” to write, and the fact that they are separate things is no reason to denigrate one or the other. Java isn’t the only victim of this: I don’t know how many times I’ve heard something like “People only write Ruby because of Rails.” Okay, maybe a lot of people do write Ruby because of Rails. And so? Does that make Ruby somehow less legitimate as a language? No: the additional tool gives Ruby power that it didn’t have before. And we can use that power for things and stuff, without trying to rip it away again so we can continue hating on what the programming experience was like before the tool came to be.
My first exposure to mock objects came from a chapter toward the end of Test Driven Development By Example, but it makes a lot more sense when you see it in practice. Suppose you have an app that needs to interact with a litany of services that are already set up on the client’s end. You want to test the code you’re writing before you have access to those services in production. This happened to be the case on the project that I got to play with at Pivotal, so the team had already defined a lot of mock objects, and we ended up working on a few more the day that I was there.
Basically, this is about defining an object that takes in the same things as the object-to-be that will interact with the outside service—we were calling this a client. It then returns the thing, or a mock version of the thing, that we ultimately want to get back from the service. So, for example, we needed to test that one of our objects would pass a username and password to a service, then get back a session token. So we made an object that takes in a username and password, does nothing with them, and returns a string that mimics said session token.
This is obviously the most basic of examples, but it is just such a basic example that illustrates the purpose of an object like this. Poor Luke got to watch me as I muddled this out: I started with plans to make the object actually do something to the information that was passed in. Then, little by little, it became clear that the object didn’t really need to do this, or to do that. The above explained implementation was, in the end, what it really needed to do. Now it serves not only as a testing tool, but also as an in-document description of the result we’ll ultimately want from the real client object—an easy-to-visualize goal that might come in handy when someone is deep in the weeds coding the real thing against an API with a lot of…personality.
Using Tests as an Outline
“Okay, let me save you here.”
When you’ve got well-written tests to learn from, the implementation writes itself…sort of.
This is another one of those things that makes sense in theory, but only hits home when used in practice. Although I’ve written tests before, it has been, when I was on my own, largely for functionality that I already knew how to code. In this situation, in a new language, doing new things, for an unfamiliar application, having a well-written test to develop from made it possible for me to code in a situation when I otherwise might not have known where to start.
I owe a lot, of course, to pairs who knew how to go about writing these tests. And when I say “the implementation writes itself,” I daresay that it can still take a minute. But…it’s possible.
Example: On one occasion, my partner and I were working on how to pass around a JSON object for a user’s authorization credentials. We finished a failing test, and the task fell to me to make it pass. First, it failed for a silly reason related to a syntax error. OK, fixed. Then it began failing for the reason we wanted it to fail, and now I needed to actually create functionality.
My pair watched his screen as I typed out a thing, backed up, then typed out the “predicate”, if you will, of a line of code without having written the “subject.” I needed to remember that this needed to be done by something. Then again, what I had on the screen looked dubious, an I wasn’t moving very fast. So I flipped back to the test to remind myself what we were trying to accomplish.
My pair moved in for the rescue:
“Okay, let me save you here.”
He raised his eyebrows and sat back to watch.
And, after one more flip back to the test and a little painfully slow reasoning, the bar went green. Mind you, I cannot stay this slow: that would be unacceptable. But having a test that clearly defined what we were trying to do made reaching the solution possible amidst a lot of (for my tenderfoot self) uncertainty. I imagine the same is true for less tenderfoot people dealing with much thornier stuff, so the test-first approach to design and development makes a lot of sense in this context, and makes more sense as the problems get harder.