Finding the right engineers for the job

IMG_9036 (1).jpg

The best engineers

"This project will be critical to our success. Let’s get our best software engineers on it…" Maybe you’ve heard something like this where you work. The question naturally arises, who are your “best” engineers?

When I first started my career, I believed that the best software engineer was one who rigorously applied the principles of good software engineering (SOLID) in a steady fashion to create something new. My thinking on this has shifted a bit. While I still very much believe in these principles, I now see them as a part of a larger picture.

The right engineers

A few years ago I attended a conference where I heard a talk from a company called CorgiBytes. They work as consultants specializing in improving and maintaining legacy code. To many software engineers, this would be the worst job imaginable. But the founders recognized that there is a special type of engineer who enjoys and even thrives on this kind of (often desperately needed) work. So they target hiring engineers with this specific strength.

CorgiBytes describes their members as, "the joyful janitors of your codebase." This quote is heartwarming to me. It reminds me that for every need, there exists a person who not only can do the job, but will derive their greatest satisfaction from doing it. What a wonderfully merciful thing that there is someone out there who will joyfully do the very thing you cannot stand doing.

True greatness, then, exists wherever a need meets a person perfectly suited to fulfill it.

The developer landscape

Okay, so if there are software engineers wired for legacy code projects, what other types of software engineers might exist? In his conference talk, the founder of CorgiBytes discussed the following model which he dubs the "developer landscape:"

chart1.jpg

Each quadrant in the model defines a particular type of developer. In the upper left hand corner you have your hacker-maker. This engineer is more concerned with "building the right thing" rather than "building the thing right." This person loves to crank out prototypes, experiment, and fail fast. In the upper right hand corner you have the craftsman-maker. This is your engineer who is more concerned with "building the thing right," rather than "building the right thing." This person wants to steadily apply the principles of SOLID. At the start of my career, I viewed this quadrant as the one strength that defined a good software engineer.

Below the x-axis are the code menders. The lower right hand corner represents the craftsman-mender. This is the type of engineer that CorgiBytes selects for. These engineers love to gut a nasty piece of code and replace it with something clear and maintainable. Finally, you have the hacker-mender in the lower left hand corner. These are the firefighters of your system. When a service is exploding or customers are in trouble, they parachute in and save the day. They are motivated by driving to a resolution quickly.

All of these strengths are needed in the context of a company like AppFolio that develops and maintains software services because each strength maps to a phase in the software lifecycle. Nothing truly new happens without the rapid prototyper, your system is quickly crippled by technical debt without your SOLID engineer, your codebase becomes legacy without your remodeler, and your services stop running without your firefighter.

Finding the right fit

In my first software engineering job, I worked in a context where the needs were almost solely SOLID and remodeler strengths. I enjoyed this work and I learned much. But I was unaware of the rapid prototyper and firefighting quadrants. I didn’t know they existed.

Things changed for me when I joined AppFolio. AppFolio’s organizational structure and focus on generalist teams suddenly exposed me to the entire developer landscape. I quickly learned that my real strength shines in the hacker quadrants. I can be quite happy in the craftsman quadrants, but I feel most alive in my work when I’m operating as a hacker. I wish I had learned this about myself sooner!

There is amazing power in finding the right fit for an engineer based on their strengths. I’ve been involved in several projects where we stacked the deck with "the best engineers," only to see these projects stagnate. We were evaluating whether an engineer was "the best" separate from the need they were intended to fulfill. Once we began asking, "which is the right type of engineer for this project?" we saw that different strengths were needed. When we made changes accordingly, we saw those projects come to life and succeed beyond what we even imagined.

Where do you fit?

What about you? Are you a great software engineer? As I said above, we have many opportunities at AppFolio for all of the developer types, as well as opportunities for developers who want to gain experience in new areas. Our organizational structure lends itself to exploration, and our journey as a company into new business verticals guarantees new adventures for many years to come. Come join us!

Programming in Paradise

January sunrise over the Pacific Ocean in Santa Barbara, CA (2018)

January sunrise over the Pacific Ocean in Santa Barbara, CA (2018)

Once a month, I wake up very early, put on a warm jacket, make myself a cup of coffee, and sneak out of the house while the rest of my family is still asleep. I drive about ten minutes up highway 101, past hills, a vineyard, lemon orchards, and a couple of ranches, to a turnout on the side of the road where I can park the car. There is a short path I walk along to a cliff with a great view. I stand at the edge drinking coffee while I watch the sunrise over the Pacific Ocean.

Santa Barbara is a unique place where, because of its location and orientation on a coastal point, the sun both rises and sets over the ocean for half of the year. Sometimes I can watch the sun sink back into the ocean from a hill near my house after I leave work and before I return home to the tornado of energy that is life with three small children.

This is a blog post about software engineering in Santa Barbara (or programming in paradise, as some call it). I’m writing it as a reflection on my time here, because I think our environment has a big impact on how we see life and on the work that we do. Even a company like Apple recognizes this -- they proclaim, “designed by Apple in California,” on the packaging for their products.

I grew up in Los Angeles and decided to attend the University of California Santa Barbara after high school. I knew even in high school that my passion was coding. I figured, “what could be better than studying what I love in a beautiful city?” It was not long before I fell in love with this place. I had an ocean view all four years of my undergraduate studies. When I finished school, I decided I had found the place I wanted to call home and would find work at one of the tech companies built upon the engineering talent which UCSB attracts to the area.

Santa Barbara has a different feel from what you might think of as a beach city. Just a couple miles inland stand the impressive Santa Ynez mountains. These mountains offer hiking trails, waterfalls, and hot springs for people to explore. Santa Barbara is sandwiched between this tall mountain range and the ocean. The unique topography of the area creates a temperate Mediterranean climate unique to coastal California. The climate is well suited for vineyards, lemon orchards, and avocado orchards, all of which you can find throughout the city.

The city has a small town feel (despite the fact that it is 20 miles long). There is a high likelihood I will see someone I know whenever I visit the grocery store or a restaurant, and for me this leads to a strong feeling of connectedness with the community. This feeling of connectedness is even stronger within AppFolio, where the people I work with are the same people I love spending time with outside of work too. This is a different experience than what I had growing up in a big city like Los Angeles.

I think the connectedness we experience and foster within AppFolio because of our location in Santa Barbara has a profound impact on our business and in the lives of our customers. More than anything, we are a customer driven company. Everything we do is based on input and feedback from customers. We do this by connecting with them and with one another. We are constantly creating prototypes and putting them in front of customers to get feedback, we invite customers to our office, we send teams to visit customer offices, and we attend customer meet ups all over the country. I’ve even had a customer invite me and a team over to his house for BBQ and prototyping.

Just a couple weeks ago I traveled to Seattle with a team of engineers. We rented a big AirBnb together and hacked on code in the offices of two customers. We built a prototype in one office, and completed a shippable feature in another office in just four hours. To me there is nothing as satisfying as seeing the smile on a customer’s face because of something we created together.

Many of our customers are small, family operated businesses. When we work closely with them, I feel it is our family connecting with theirs. They trust us because we listen to them and we are top notch engineers. We love them because they give us a reason to come to work each day. We are a growing company but we believe that part of our success depends on us “keeping it small” so that no customer or teammate gets lost as a cog in a big machine.

We are a group of passionate engineers. We work hard and get the job done. We take pride in our work as engineers, and we never want to let our teammates down. In my experience, the best engineers are the ones who realize that being brilliant will only get you halfway there. The other half depends on a willingness to roll up your sleeves and make things happen. These engineers love problems because they recognize them to be opportunities.

We work exceedingly hard, but we are not running in a rat race. Our location in Santa Barbara reminds us that there is more to life than work. Across our engineering group we have avid rock climbers, cyclists, runners, surfers, volleyball players, soccer players, softball players, hikers, and kite boarders. Santa Barbara is a place that attracts people from all over the world because of its natural beauty, the influence of the university, and its deep cultural roots. There are many festivals throughout the year, including Fiesta, the French festival, the lemon and avocado festivals, the solstice parade, and the famous Santa Barbara film festival. Visitors and residents enjoy a variety of unique restaurants throughout the city.

Santa Barbara has much to offer beyond work, but for those times you need something from the big city, a short drive to the south will take you to LA. We are far enough from LA that the air is free of smog and the highways are not a constant traffic jam, but we are close enough for a day visit. To the north you can enjoy wine country. People from all over the world come here to vacation and we get to live here!

Santa Barbara is a special place to me and to so many of us at AppFolio. This is the place I met my wife. I proposed to her standing on top of the mountains overlooking the city and ocean. I loved what Santa Barbara had to offer when I was a college student who had just moved away from home, and I love it now as a place where my wife and I can raise our three young boys. The beauty of the city and surrounding nature, the people I have come to love, and the opportunities I have to build something awesome with an amazing team are the things that keep me here.

We’ve got a thriving business at AppFolio and are hiring great engineers who want to have real impact in customers’ lives. If this is you, then come program with us in paradise!

November sunset over the Pacific Ocean in Santa Barbara, CA (2017)

November sunset over the Pacific Ocean in Santa Barbara, CA (2017)

Thinking About Innovation

Drawing a Blank

It’s a brisk autumn morning. You just woke up and you lay in bed waiting for your brain to load the essential bits of information required to start your day. Suddenly, you remember. You just finished a big project and nothing new will be lined up until sometime tomorrow. Today is a free day and you get to work on whatever you want. You get out of bed and start getting ready with a growing sense of anticipation. The sun is shining, there’s an invigorating chill in the air, and a flood of ideas begins to fill your mind. Anything is possible.

You’re one of the first people to show up in the office this morning. You sit at your desk staring at the blinking cursor of your text editor. What to do...so many options. “I could learn a new front end framework. Or I could write that one Rails app I’ve been thinking about in my spare time. Maybe I should learn Haskell! Or I could try to untangle that horrible piece of code that’s been nagging at me.” Then, the first email of the day arrives in your inbox. It looks important and you figure you’d better respond to it so that it isn’t hanging over your head the rest of the day.

A few hours later it’s time for lunch and you realize you’ve spent the majority of the time fending off emails and trying to figure out where to get started. Half of your cherished free day is gone and you’ve got nothing substantial to show for it. Anything was possible, but now it seems like nothing is possible.

The Raw Materials of Innovation

Why is innovation so difficult? On the one hand, many of us have endless lists of ideas we’d love to spend time pursuing. We go about our day-to-day jobs yearning for a moment of freedom when we could forget about our responsibilities and try something totally new. And yet, when that moment presents itself to us, we feel unprepared or lost. The things which we thought would have motivated us seem to lose their appeal. Our interest fades and we hop from one idea to the next, never really getting anything done.

I recently attended a workshop on innovation and came away with some useful ideas for making innovation happen. One of the most valuable things I came away with is a more concrete definition of what innovation actually is.

So what is innovation? What makes something innovative? The common belief (or at least my prior belief) is that innovation is a novel idea - doing something that hasn’t been done before. So, if we want a group of people to be innovative, we should get together in a room and have a brainstorm of cool ideas. The more unique the idea or the approach, the more innovative.

The speakers at the workshop I attended provided a more useful definition. An innovation is the fulfilling of a customer’s need with a technology in a novel way. So, there are two essential components to innovation: customer needs and technologies. Innovative ideas result from the combination of needs and technologies in new ways.

At a previous job, I spent a couple of years working on a project which, in the end, had no real customer. We solved many technical challenges with this project and I’d even say that many of the ideas in the system we designed were quite novel. But, according to the definition above, we weren’t truly innovating because we weren’t solving a need. We were just building technology.

Customer needs and technologies are the raw materials for generating innovative ideas. This may help us better understand why it can be so difficult for us on that glorious free day to get beyond a blinking cursor in an empty text editor. Perhaps we lack the necessary building materials. Think about the times when you’ve had the best ideas, been the most engaged with your work, and been the most productive. For me, these times occur when there is a clear need and I’ve got (or can learn) the technical know-how to meet it.

So, the answer to moving forward is not to have a better brainstorm session, it’s to acquire more information. Are you the person who knows all sorts of programming languages and frameworks but can’t think of any good use for them? You need to spend some time finding a real customer problem that you can match up with some of this technology. On the other hand, maybe you’ve got a list of complaints from customers and don’t know where to begin. In this case, you may need a better view of the technology landscape.

Finding the Need

It probably goes without saying that finding real customer needs takes work. Most software companies have a product team whose mission is to understand and evaluate the importance of customer needs. In such cases, it is crucial for product folks and engineers to work together as a team. In general, product will supply the customer needs and engineers will supply technology. Together, they generate innovative solutions by evaluating which technologies are the best fit for the needs.

The degree to which a team is able to innovate is dependent upon how deep a team can go with the customer need. The deeper the team goes, the wider the solution space and the more technologies there are that can be applied to the problem - hence, more opportunity for innovative ideas. Any product manager will tell you that customers love to provide solutions and rarely begin by talking about the underlying need. By asking the right questions, the team can gain access to the deeper problem. The customer may say he needs a better horse, but what he really needs is a faster form of transportation. Or maybe what the customer really needs to spend less time traveling to and from work. The pool of available solutions increases dramatically based on how the problem is framed.

The speakers emphasized that the best opportunities for innovation come from needs that customers don’t realize they have. These are the needs that have likely never been addressed by any combination of technologies. According to the speakers, a customer will never realize there is a need unless they perceive some alternative. So just asking customers about their needs is not sufficient because you are likely to wind up with a list of problems for which solutions already exist.

One remedy for this is actually quite simple: go live life with the customer. The longer you sit with a customer as they use the product, the more opportunities you will have to see the hidden needs. If you find that the customer is using the product in an unexpected way, this could also indicate a hidden need.

Finding the Technology

So you’ve lived life with a customer, you’ve found a high impact need, now what? The speakers described an interesting approach to finding the right technology for a truly innovative solution. First, think about the core problem that must be solved and ask yourself “what other industry has faced a similar challenge?” Try to think far outside of your own industry. Then, go talk to folks in that industry and see what they’ve done to solve the problem. See if you can take their solution and apply it within your own problem domain.

The speakers gave several fun examples of this approach. When NASA began designing their first space suit, they had difficulty attaching the fabric and metal portions of the suit. Apparently, this is a pretty challenging thing to do, especially when someone’s life depends on the strength of the fabric/metal interface. NASA looked outside the space and military industries for a solution. They found an unlikely partner in the Playtex corporation, a popular producer of women’s undergarments. The problem of attaching fabric to metal is common in the design of bras and NASA was able to leverage Playtex’s experience in this domain to overcome their design challenges. At the time, NASA was embarrassed to admit that a portion of their space suit was developed by a bra company, so they contracted out to another company with the understanding that the real work would be sub-contracted to Playtex.

When looking to other industries for technologies, it’s wise to ask the people you talk to, “who has a similar problem, but even worse?” This can lead you people who have already solved the problem in really creative ways. The medical division of the 3M corporation was looking for a way to help surgeons prevent incisions from getting infected. They asked surgeons, “who might have an even harder time with infections than you?” This led them to investigate veterinarians, battlefield medics, and surgeons who operate on patients whose immune system has been compromised by chemotherapy. The key innovation for 3M’s product ultimately came from these discussions.

Similarly, the development of anti-lock brakes in cars came from an industry with a more extreme version of the problem. In this case, it was the airplane industry. Almost everything about this industry is more extreme. The stakes are higher, the speeds are faster, and icy runways cannot be salted because salt can damage aircraft. This made it a great candidate for solving a similar problem with automobiles.

When Should We Innovate?

How deep should a team dive into the customer’s needs? Indeed, opportunity for innovation increases, but so does the uncertainty for the project. With a large solution space, the team could spend ages finding and evaluating available technologies. And there is no guarantee that any of these technologies will pan out in the end. At the same time, there is the potential for immense gains. What if the team discovers a solution that eliminates an entire class of problems for the customer? Or, what if they find a solution that allows them to move ten times as fast? These could be game-changers for an entire industry.

One answer is that it depends on the business context. Maybe there is a short window of opportunity to make some key sales. Providing a feature within this timeframe will allow the company to get the sales while they are still available. The product manager may decide that these sales are more valuable than the potential for innovation and therefore focus the team on the immediate need. This may make sense given the context.

I think there are two dangers to avoid when approaching a project in this manner. First, it’s possible for a team to jump to a solution too quickly under pressure from the context. They may overlook a very simple solution to a deeper need and add unnecessary complexity to the product. This danger can be somewhat mitigated by at least timeboxing a brief exploration of the solution space.

The second danger occurs when the context forces the team to approach every problem at a surface level. History is replete with examples of organizations that failed because they got locked into addressing customer needs at a single level. I’m thinking specifically of Blockbuster and Kodak right now. These companies were once immensely successful, but they continued to solve problems the same way. They were ultimately destroyed by innovations which met customer needs at a deeper level.

Innovating Within an Organization

It’s clear that a company must innovate in order to survive long-term. In the initial startup phase of a company there isn’t much to lose and everything looks like an opportunity. Entrepreneurship and innovation are center stage. The problem is that companies become increasingly risk-averse as they grow. There is no immediate reason to change the way things are done when a company is enjoying a period of great success - if it ain’t broke, don’t fix it.

The speakers at the workshop talked about the value of a storm in these cases. A storm is some event that causes the leadership in a large organization to question the future. Examples include a competitor success or a new technological breakthrough. These events provide the motivation to accept more risk and make changes. In my view, a reliance on storms does not seem like a great way to run a business. Storms are unpredictable and may leave too little time for an organization to adjust. On the other hand, you might try to generate artificial storms, but this will eventually erode trust within the business.

Innovation is uncertain because it involves questions like, “is this problem even solvable?” An innovative solution could take many years to implement. The key to innovation within a larger organization is the recognition that uncertainty is not the same as risk. Uncertainty comes from our inability to predict the future. Risk is the impact of this uncertainty to the business. If you can find ways to mitigate risk, then you can still innovate even in the largest companies.

How do you mitigate risk? The same way you would do anything else with an agile mindset: start small and work in small increments. Don’t ask for a big budget or a large team right off the bat. Instead, find ways to show progress at each phase with the minimal resources at your disposal. As you demonstrate progress you may find that the organization is more willing to invest in your idea.

The 3M corporation is often cited as a company with a culture of innovation. Employees within 3M are allowed to work on whatever they want as long as they fulfill their day-to-day responsibilities reasonably well. It’s an environment that allows time for curiosity and exploration. The Post-It note was invented by a chemist at 3M who stumbled upon a unique adhesive while performing some experiments as part of a side-project. He knew his technology could be useful for something and he spent the next decade trying to figure out what that useful thing was.

Innovating at AppFolio

So how does all of this connect with work at AppFolio? Our second company value is: Great, innovative products are key to a great business.

I see several ways we strive to embody this value. First, product and UX spend an insane amount of time with our customers, talking to them, watching them, and deciphering their needs. They visit customer offices, they hang out with customers at meetups, they talk on the phone with customers all day, and they give customers demos of prototypes and new features.

As an organization, we are learning more and more how to drill down to the deep customer needs and make those the focus of our engineering efforts. We generally work without strict deadlines and this gives engineers a bit more freedom to ask questions and explore.

AppFolio provides engineers with a few regular opportunities to innovate. Every other Friday is Future Focused Friday. This is a day when engineers are free from their day-to-day responsibilities. They may use this day of work however they wish, without any accountability. Some engineers spend the day reading a book to deepen their understanding of a particular technology. Other engineers have projects that they are working on. For example, one engineer built a web app for sharing appreciations of other employees with the company. Still other engineers work on improving some part of our infrastructure or product. One engineer recently implemented an optimization in our continuous integration system which cut the runtime of our test suite in half.

Future Focused Friday is a zero-risk day. It frees engineers to try something crazy, like programing a drone to perform a property inspection or create a wireless submetering device. Because this day is already set aside, it costs the company nothing extra. In fact, these experiments may prove to be future streams of revenue for the business.

AppFolio hosts two hack days each year. Hack Day is similar to Future Focused Friday in that engineers can work on whatever they want for the day, but Hack Day tends to emphasize team projects and includes members of the company outside of engineering. A lot of cross-pollination between various parts of the company occurs on Hack Day. It’s also just plain fun. There are costumes, funny presentations, and beer. This gives the day a feeling of lighthearted playfulness and creativity.

We’ve recently introduced a new event to promote innovation called Shark Week. During this week, teams present innovative ideas they think will create value for our customers to a panel of judges. The judges select the project they think will have most value and the corresponding team has one month to implement it.

These are some basic structures and events that aim to promote innovation here at AppFolio. But just providing events doesn’t automatically make innovation happen. It takes an atmosphere of playfulness, curiosity, exploration, and, as I argue in this article, intense focus on customer needs and technology.

It's a Wiring Problem

IMG_2616 I once had a mentor who liked to say, “If you can reduce it to a wiring problem, you’re half-way there.” It took me a while to really understand what this means and why it is useful advice for a software engineer. In this post, I’m going to do my best to explain it and provide you with some practical applications. I hope you find it helpful!

When we aren’t at work, we move around in a world of physical objects. Many of these objects are quite complex: airplanes, iPhones, refrigerators, and Ikea furniture. Some of these are composed of hundreds of thousands, or even millions of intricate parts (Ikea furniture), all delicately assembled in a very precise configuration. It’s easy to forget how complex some of these objects are because they present such a simple physical interface. To the consumer, the iPhone is just a solid flat rectangle.

Even easier to forget are the complex processes used to fabricate these objects. How many different vendors, machines, and stages are involved in the creation of an iPhone? It would take us a long time to count. When I was a kid, I loved the episode of Mr. Roger’s Neighborhood where they went on a field trip to the crayon factory. It amazed me that the creation of something as simple as a wax cylinder could require so much behind-the-scenes infrastructure.

Ok, so what does this have to do with my mentor’s favorite phrase and your work as a software engineer? As developers, we spend the majority of our time modeling the world around us in software. We model physical objects, processes, and relationships to name a few. We strive to break these things down into independent concepts that we then assemble together within a software system.

These systems can become just as complex (and perhaps more so) than the hardware configuration of an iPhone. If you’ve done your job well, then you can walk over to a whiteboard, draw out a whole network of software components, and show how these components relate to one another. Component A sends a message to component B which forwards it along to these three other components, C1, C2, and C3. These components perform callbacks on component B when they have finished their work, and so forth.

IMG_2613

Great! You understand how the software system will perform it’s job once the components have been arranged properly. But how were these components arranged in the first place? How did component A ever come to know about component B? How did C1, C2, and C3 come to know about the callbacks for B? The answer is that something had to wire them together prior to the interactions you drew on the whiteboard. You drew half of your problem on the whiteboard, the other half is wiring.

Now maybe my point is clear. Just as with a complex physical object, the components in a complex running software system must be meticulously wired together. And the machinery for performing this wiring can often be just as complex as the system itself. Whenever you are solving a problem using software, remember that you are actually dealing with two separate subproblems: modeling the domain and wiring the components. Now you might be thinking, “That’s not true. I’ve worked on complex software projects and didn’t have to think much about wiring.” I’m wondering whether the code for the project looked something like this:

class Pen
  def initialize
    @ink_cartridge = InkCartridge.new(ink_color: :blue)
  end

  ...
end

class InkCartridge
  def initialize(ink_color:)
    @ink = ColoredInk.new(ink_color)
    @ball_point = BallPoint.new
  end

  ...
end

class BallPoint
  ...
end

If so, then I’d say that the wiring aspect of the system is still there, but it’s somewhat hidden. In this example, the problem of assembling a ballpoint pen and the problem of using a ballpoint pen are intermingled. The pen is creating and storing a reference to the ink cartridge, the ink cartridge is creating the ballpoint, and so on.

In the physical world, I doubt you could find a pen that created and assembled itself. But, I see this type of design all the time in the software world; and it causes trouble, as the system’s design evolves, because it attempts to address two very different problems with a single solution. Your thinking, as a developer, is muddled as you write the software because you are constantly trying to solve two distinct problems at the same time.

To make matters worse, the single solution you devise binds the two problems together, making them inseparable. Now what happens if the requirements for how the components are arranged changes? You begin to compromise the integrity of your model to meet the demands of the wiring problem.

Instead of trying to solve both problems simultaneously, what if you focus on one problem at a time? You will need to solve both problems regardless of how you approach the design, but at least if they are separated, you will have the chance to give each problem due thought. First, focus on the primary goal -- how to model your problem domain in software. Once you can confidently draw a diagram of control flow through your components for various use-cases, it’s time to turn your attention to the secondary problem of wiring these components together.

So what tools do you have at your disposal for solving the wiring problem? Well, if we are attempting to address our two problems separately then the code for solving each of them should be separate. I typically refer to the code used to wire components together as a factory. Factories can take several forms, each with its own strengths and weaknesses. In the following sections, I will describe three forms of factories.

Class Factory Methods

Let’s go back to our ballpoint pen example and use a factory to pull out the wiring code. The code fragment below shows one way we might do this.

class Pen
  def self.create
    new(InkCartridge.new(ColoredInk.new(:blue)))
  end

  def initialize(ink_cartridge)
    @ink_cartridge = ink_cartridge
  end

  ...
end

In this example, we’ve pulled the creation of the ink-cartridge out of the pen initializer and have added a new class method, create(). This method instantiates the ink-cartridge, injects it into the pen’s initializer, and returns the new pen instance. The pen initializer knows nothing about the ink-cartridge it is given. It just assigns the ink-cartridge to a member variable for use within the pen instance.

There are a few advantages with this simple refactor. First, it gives us options for how pens might be created in the future. For example, we might add several creation methods as the system evolves:

  • create_ballpoint_pen()
  • create_whiteboard_pen()
  • create_ipad_stylus()

Some have argued that software architecture is an exercise in deferring decisions and keeping your options open. If this is true, then our refactor supports good architecture because it has opened up a world of options while still keeping pen creation simple for the common cases. Users can take advantage of our factory methods, or they are free to write their own wiring code outside the scope of the pen class.

A second, related advantage is that this method gives us greater freedom to mock the ink-cartridge in test code. In your test setup, just create the mock object and pass it directly into the initializer. I recognize that there are mocking libraries in Ruby which allow you to stub class initializers so that they return mocks, but I find the injection technique much easier to understand, especially when you’ve got a test involving several pens.

The strength of this technique compared with other factory approaches is simplicity. It decouples the wiring from the main problem with very little extra effort. At the same time, it keeps the factory close to the class that it creates (it’s in the same file). Readers will have little trouble understanding how pens are created just by looking at the pen class.

This approach does have at least one potential downside -- it makes pen class directly dependent upon the ink-cartridge class. The two classes cannot be separated even though instances of the pen class don’t care what type of ink-cartridge they refer to. If you wanted to turn your pen class into a library, you would have to include the ink-cartridge implementation as well. This may or may not make sense depending on your context.

Factory Modules

Now consider the case where there are more than a few possible pen configurations. Rather than overloading your class with fifty different class factory methods, you might consider extracting this code into a factory module. You could take this further by grouping similar configurations in separate modules. For example:

module WhiteboardPenFactory
  def create_thin_expo_marker
    ...
  end

  def create_thick_expo_marker
    ...
  end
end

module PermanentPenFactory
  def create_standard_sharpie
    ...
  end

  def create_twin_tip_sharpie
    ...
  end
end

module BallpointPenFactory
  def create_standard_pen
    ...
  end

  def create_erasible_pen
    ...
  end
end

The strength of this approach is in the complete separation of wiring code from the problem domain. The pen class knows nothing in particular about the components of which it is composed and will not need to change when new pen variants are added to the system.

Similarly, the factory modules aren’t required to generate instances of the pen class. There may be, in fact, several pen classes with completely different implementations. Users of the factory modules needn’t be concerned with the specific type of pen that pops out of the factories, as long as each implementation adheres to a common interface.

The factory module approach does come at a cost. Because the wiring code is totally separated from the domain, readers will likely have a harder time understanding where and how pens are created, especially if our factory modules are spread across a large codebase.

Factory Classes

Sometimes wiring is not as straightforward as calling the appropriate factory method. The first factor that can complicate wireup is the need to maintain state across several invocations of a factory. In our pen example, what could we do if each pen needed a unique serial number? We might achieve this by maintaining a counter to generate successive serial numbers, passing each serial number as a parameter to the factory method.

However, this approach is cumbersome for the user, who shouldn’t have to care about the details of how the serial numbers are generated. Instead, these details should be hidden within the factory itself. Why not write a factory class that can maintain the counter state internally? The factory would store the counter as a member variable and increment it whenever it created a new pen. This process is transparent to the user of the factory class.

IMG_2614

Another reason you might create a factory class is if you have a system that needs to create components but doesn’t care about the specific type of component created. Imagine we are modeling a school classroom. The classroom must provide one writing implement for every student who walks through the door.

When we create the classroom, we can inject a ballpoint pen factory into its initializer. The classroom calls create_pen() on the factory instance each time a student enters the classroom. Once again, this approach helps us keep our options open for future extension. For example, we might decide that students will write on whiteboards. In this case, we simply inject a whiteboard pen factory into the classroom. The classroom itself doesn’t need to change in order to support the new behavior.

IMG_2615

This approach is probably the most flexible of the wiring options we’ve discussed. It allows the developer to fully separate wiring code from domain code, maintain wiring state, and inject the wiring system into other components. On the other hand, this is also the heaviest approach. If you create a factory class for every class, then you wind up doubling the number of classes in your system. This places a great burden on someone else who is trying to make sense of your system. If you find yourself creating factory classes for your factory classes, you’ve definitely gone too far with this approach.

Sometimes you need the ability to maintain state and inject a factory, but creating a whole new class just feels overkill. In these cases, lambdas fit the bill quite well. They carry context along with them, they can be passed around just like any other object, and they can act as factories. But be forewarned, your lambda can get pretty unwieldy if you have a significant amount of wiring code. If your code starts getting hairy with large lambda definitions, you’re probably best served by extracting it into a class.

I’ve found that it works well to start with the class factory method and refactor to meet the demands of new requirements as the system evolves. Refactoring is generally not too difficult because you’ve already got your wiring code and your domain code separated. It’s usually just a matter of moving the wiring code around into the right location.

Concluding Thoughts

If you’ve done any reading about software engineering, you’ve likely come across the SOLID acronym which attempts to summarize some of the basic principles of software design. In my view, the stuff we’re discussing here is all about SOLID. For example, the separation of wiring and domain code is a direct application of the single responsibility principle. Our example of injecting a pen factory into a classroom is an application of the Liskov substitution principle (classrooms may use any sort of factory that produces a compatible pen), the dependency inversion principle (the pen factory is injected into the classroom), and the open-closed principle (the behavior of the classroom can be extended without modifying the class by injecting a different kind of factory).

I waited until the end of the article to mention these connections to SOLID because I believe that learning the principles from the top down can sometimes leave a person with only a vague sense of their value and how they might be practically applied. This article represents how I personally came to understand and internalize most of the principles of SOLID.

My aim in this article was to emphasize the need for separation between wiring and domain code and provide some simple tools for achieving this separation. In general, my software design approach can be summarized as follows:

  1. Model the domain draw diagrams showing how the components interact.
  2. Ask how the components get wired up in the first place and draw more diagrams.

When thinking about which wiring tool to reach for, I usually reach for the simplest one that will satisfy my current needs and plan on refactoring later when requirements change. I’ve found this approach to yield flexible designs that are able to cope well with changing requirements. As with anything, it’s possible to go overboard with this stuff, so if you feel like the code is working against you, it might be time to stop and re-evaluate. This topic is closely related to the topic of object composition. If you are interested in learning more about that, take a look at my previous post. Thanks for reading!

A Composition Regarding Inheritance

Observations from an Object Oriented Interview

I've had the opportunity to interview a number of candidates for software engineering positions on my team over the past few years. In most of these interviews, I presented the candidate with the same design question: “Let’s say you were designing a traffic simulation. In this simulation, you’ve got a car. It can start, stop, move forward, and move in reverse. Yes, it’s a really boring simulation.” I then asked the candidate to draw this design on a whiteboard. In every interview, I got something that looked like the following picture:

Ok, so far, so good. The candidate thinks of a car as a component in a software system that has capabilities matching my requirements. Then I said, “let’s make this simulation more interesting by including two cars: A and B. Car A and B are similar, but the way car A starts and stops is different from car B. Please update your diagram to show how you would accommodate this requirement while minimizing the amount of repeated code.” In almost every case (and especially when the candidate was fresh out of school) I got the same reaction: a confident smile appeared on the candidate’s face, as if to say, “I’ve done this a million times...textbook object oriented programming.” The candidate returned to the whiteboard and drew this:

composition-regarding-inheritance

“Okay, next task: I want to introduce a new car, say car C. Car C starts like car A, stops like car B, and has its own way of moving forward. Please update the diagram to accommodate this requirement while minimizing the amount of duplicated code.” Long pause. Eyebrows furrowed. At this point, the solutions people gave begin to diverge. C++ folks began struggling with multiple/virtual inheritance, Ruby folks started talking about mixins, and Java folks were stuck. Regardless of the language, the diagram ended up with some more lines to indicate inheritance relationships. I simply proceeded to ask the candidate to add new cars with different combinations of functionality. So for example, I asked the candidate to add car D which moves forward the same way as C, starts like car A, and stops like car B. I continued this process until the whiteboard diagram was an incomprehensible mass of circles and arrows.

My goal in this exercise was not to torture the candidate or to see if they “knew object oriented programming,” but to watch their thought process as they realized that their design approach was brittle and needed some rethinking. At some point the exasperated candidate stopped and said, “there must be a better way,” and this was generally the beginning of a fun and constructive design discussion.

The next question I asked in such interviews was, “can you think of a way to redraw this diagram without any inheritance or mixin relationships.” Many people really struggled with this -- they thought that I was asking a trick question. “Here’s a hint, what if you had a class called Ignition? How would you use it in this picture to remove some of these other class relationships.” This was usually the moment of epiphany for the candidate. The vocabulary of our discussion rapidly expanded from talking about the methods of the Car class to new concepts like Brake, Accelerator, and Transmission. Pretty soon our diagram looked like this:

composition-regarding-inheritance

Pretty straight forward. This design approach is called class composition. Many of the candidates said, “wow, that solution should have been obvious to me.” Maybe so, but I must admit that I had their very same approach to design when I first began working at a software company. To some, the software requirements described in the exercise may seem pathological, but this has not been my experience. I feel that the exercise is actually a pretty good picture of the way some of the first software projects that I worked on developed after I graduated. I encountered many of the same frustrations that the candidates I interviewed did, just on a larger scale.

Those who are familiar with Ruby will point out that mixins are another way to meet the requirements of the system described above. For example, we might create Startable, Stoppable, and Movable mixins that could be included in any combination within our different car classes. But, in the context of the interview exercise, I could still push my requirements further. “Okay, let’s say we want a new car X that starts like car A on hot summer days, but starts like car B on cold winter days. Please update your diagram to accommodate this requirement while minimizing the amount of repeated code.” This is trivial with a composition-based design. Our car X just updates its ignition reference from IgnitionA to IgnitionB on cold days. This is more difficult to achieve with Ruby mixins since, again, we declare an explicit class relationship before runtime. Or how about another requirement, “let’s say that the start sequence for car Y begins with the sequence for car A and ends with the sequence for car B.” Again, the mixin relationship represents a design commitment we make up-front that reduces our options later in the game. Traditional inheritance and mixins are very similar in this respect.

Comparing Composition and Inheritance as Design Tools

Why is it that many of us have a tendency to approach design problems using inheritance rather than composition? One reason could be the way object oriented programming is presented in school and in textbooks. Whenever I asked interview candidates why they chose inheritance over composition, the general response was that inheritance describes an is-a relationship and composition describes a has-a relationship. They initially felt that the interview exercise was best suited to an is-a relationship, so that’s what they went with. Indeed, this is exactly how I learned to think about object oriented programming. The problem with this view is that the classification is subjective. You can almost always think of a way to convert an is-a relationship into a has-a relationship and vice-versa, just like I described in my example above.

The is-a/has-a paradigm can give one the impression that composition and inheritance are two similar tools on equal footing that can be used interchangeably to achieve a similar goal. In reality, these tools have very different strengths and are therefore suited to different problems. It is wise, then, to consider these strengths in choosing which tool to apply in a particular situation.

I've already alluded to some of composition’s strengths in the examples above. These include a greater opportunity to defer design commitments and a more natural way to talk about and model the problem space. The first is due to the fact that composed relationships are based on object references, not class references. In the car example above, we refactored our design such that a car is composed of several internal components. In this scheme we have the option of changing the references to these components at any time, including runtime. That is to say, we could design a system in which a car could be reassembled with totally different components all while traveling 80 mph down the highway. That’s pretty powerful. With inheritance, we would either need to stop our software and modify the inheritance hierarchy or, if we were working in Ruby, engage in some metaprogramming wizardry. The point is, with composition we minimize early design commitments and maximize the likelihood we'll be able to accommodate unexpected requirements.

The other strength is somewhat less tangible in a discussion like this, but in my view no less important. Composition is the way we generally think about how systems work in the physical world. It therefore lends itself nicely to the development of clear vocabulary for talking about a problem. If I asked you to tell me how a blender works, you would probably begin by identifying the key components: blade, motor, power cord, switch, carafe, and lid. Each component in the system has a specific job and works in concert with other components in the system. For a given blender use-case, you could draw me a diagram of how the parts interact with one another -- the motor turns the blade, the lid locks into the carafe, the switch opens the flow of electricity through the motor. On the other hand, you could consider an inheritance-based approach. You might begin by describing a blender as a type of kitchen appliance. Appliances consume electricity to perform work, where the work performed depends on the specific type of appliance. In the case of a blender, the work performed is the rotation of a blade to chop or liquify food. While this is a reasonable definition for a blender, it tells us little about what a blender is from an implementation point of view. Based on this description, it would be more difficult to draw a diagram of what happens internally when someone presses the “on” switch.

Our blender discussion brings us back around to the is-a/has-a distinction, but perhaps with the opportunity for a deeper understanding. The composition-based design is better suited for describing how our blender accomplishes its job whereas the inheritance-based design is better suited for defining what a blender is. Do you see the difference? The definition of a blender can apply to a wide range of blender implementations. Silver Bullet and VitaMix are both blenders but they have different implementations. Our view of a blender as the composition of many parts represents the description of a specific blender implementation. Inheritance therefore finds it’s strength in describing the relationships between class interfaces apart from implementation. Composition by its very nature is an implementation detail and finds it’s strength in modeling the implementation of a system. This observation has major implications for software design in explicitly typed languages like C++ and Java. In these languages, we can define a hierarchy of pure interfaces (that is, classes with method definitions but no corresponding implementation). We can then refer to objects at different levels of abstraction in various contexts by moving up or down the interface inheritance chain. The classes which implement these interfaces, however, can follow a different structure and can be changed without modification to the interfaces.

I won't get into a detailed discussion of interface-based design here -- it’s a topic that deserves it’s own discussion. I expect that most of the people reading this article are Rubyists. Ruby does not provide an explicit interface concept so the benefits of inheritance for interface-based design are diminished. In the Ruby realm I most often see inheritance applied as a method of code reuse. While code reuse is certainly achievable with inheritance, I hope our discussion so far has provided some convincing evidence that this is not what it excels at -- just consider the frustrated interview candidate from the beginning of this article. Composition for code reuse is more flexible than inheritance and typically comes at little cost.

Okay, so what are the strengths of inheritance in a language like Ruby? I view inheritance in Ruby as a tool of convenience; one that can reduce boilerplate code in cases where several classes share the same method signatures. Consider again the traffic simulation design in my interview question. Each type of car in this example delegates its methods directly to the internal components. This leaves us with a bunch of classes that repeat the same delegation pattern in each method, just with different types of components. This boilerplate code can be removed with the creation of a base class which operates on components provided by subclasses. Notice that we’ve used composition and inheritance together to achieve a flexible and convenient design.

composition-regarding-inheritance (2)

That said, I think there is an even better approach that does not use inheritance if our design does not require a separate class for each type of car. In this case, we can implement a single car class and a car factory that builds cars. The car factory creates different configurations of components depending on what behavior we want in our car and injects them during creation of the car object. See the figure below:

composition-regarding-inheritance (3)

What about the role of mixins in Ruby? Again, in my view mixins provide a measure of convenience, but do not represent a tool that is well suited as the foundation of a system’s design. The Enumerable mixin is a good example to consider. The Enumerable module in Ruby requires that classes which include it implement the each method (and optionally the <=> method). It uses these methods to provide a rich set of traversal, sorting, and searching methods in the including class. Ruby mixes the Enumerable methods into several basic data types including arrays, hashes, and ranges. Working with these types is quite pleasant since they allow all of these methods to be called directly on the objects themselves and allow for several method calls to be chained together. Most people would probably see the Enumerable module as a great example of code-reuse, but I disagree. The same degree of code-reuse can be achieved through simple class methods within a module. Here’s what the call to any? would look like if Enumerable were written in this way:

Enumerable::any?(numbers) { |number| number > 10 }

The main benefit of the mixin approach is ease-of-use. It is more natural for a developer to call the methods directly on the objects, especially when several method calls need to be chained together. Calling class methods in a module is still doable, but would be quite cumbersome in such cases. So the advantages we see in the Enumerable mixin example focus on syntactic niceties. They help developers read and write code (and this is certainly important!), but they do not significantly contribute to a developer’s conceptual understanding of a system, which I believe should be considered as a first priority when designing software.

Concluding Thoughts

The longer I work with object oriented programming languages, the more clearly I see a priority in tools these languages have to offer. In my opinion, composition is the bread and butter of design tools. It’s the tool I reach for first when I’m trying to model the solution for a problem. Doing this allows me to develop meaningful vocabulary for talking about each aspect of the solution. I find that I can draw diagrams showing clear interactions between components and this allows me to verify my understanding of the solution before I spend time implementing it. Once I have implemented my solution using composition, I sometimes notice repeated or excessive boilerplate code. In such cases, I will turn to my tools of convenience: inheritance and modules (domain specific languages and metaprogramming also fall into this category for me). These tools are the icing on the cake. The system can be understood without them, but they can make using or extending the system more pleasant.

When I first learned about object oriented programming I read a book that made this recommendation: “prefer composition; use inheritance judiciously.” This bit of wisdom stood out to me mainly because I didn’t really understand why I should follow it. I’m sure the book provided some justification regarding flexibility, but not in a way that I could grasp at the time. It was only after several years of pain from ignoring this advice that I began to see the basis for it. My hope in writing this article is that others would be provoked to careful thought regarding the application of inheritance in object oriented languages and perhaps avoid some of the same frustration I encountered. Thanks for reading!