John Bohn

My thoughts on software, tech, business, and more

Single Responsibility Principle: A SOLID Week

The Single Responsibility is often written as “A class should have only a single responsibility.” Well that’s a bit general. A definition like that is a bit hard to reason about. It’s not very actionable either. There’s no sense of how to define responsibilities. Nothing that really gives you any hints on where to draw boundaries. Taken to the extreme, it actually lead to overly complex systems. There is such a thing as too much decomposition.

I think there’s a better definition of the Single Responsibility Principle and that is: “Design classes so there should never be more than one reason for a class to change.” Now that’s actionable advice. And, it’s defined within the context of the change that is bound to happen.

Let’s go into a practical example of how I might apply the Single Responsibility Principle.

Say we need a new feature that generates a report and sends it to Jill in Finance. Jill is really nice and always puts on a new pot of coffee on when we run out so we’ve decided to prioritize the feature for her. A basic approach could be to do the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Reporter
  def send_report
    users = User.where("last_logged_in_at >= ?", 1.week.ago)

    users.each do |user|
      message = "Id: #{user.id}\n"
      message += "Username: #{user.username}\n"
      message += "Last Login: #{user.last_logged_in_at.strftime("%D")}\n"
      message += "\n"
    end

    Mail.deliver do
      from "jjbohn@gmail.com"
      to "jill@example.com"
      subject "Your report"
      body message
    end
  end
end

Now would be a good time to ask ourselves, what is likely to change. A few likely examples are: * Jill gets fired and no longer works at the company so someone else needs to get the email * The format of the email is pretty terrible. Could I get a spreadsheet? * Could you send a monthly report too? * Could you include when the user signed up?

All of those things would change this single class. Let’s start by isolating a few concerns.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Reporter
  def send_report
    message = generate_message(users)

    deliver_report(message)
  end

  private

  def users
  end

  def generate_message(users)
    message = ""
    users.each do |user|
      message += "Id: #{user.id}\n"
      message += "Username: #{user.username}\n"
      message += "Last Login: #{user.last_logged_in_at.strftime("%D")}\n"
      message += "\n"
    end

    message
  end

  def deliver_report(message)
    Mail.deliver do
      from "jjbohn@gmail.com"
      to "jill@example.com"
      subject "Your report"
      body message
    end
  end
end

The code isn’t really any more extensible yet, but the responsibilities are starting to show themselves more. 1. Get a list of users given a criteria 2. Format the collection of users into a report 3. Deliver the report

Let’s break the system down along these lines.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class ReportDataCsvCompiler
  attr_accessor :data
  def initialize(data)
    self.data = Array(data)
  end

  def format
    heading << body
  end

  private

  def heading
    data.first.keys.join(",") << "\n"
  end

  def body
    data.each_with_object("") do |str, row|
      str << row.values.join(",") << "\n"
    end
  end
end

class User
  scope :logged_in_this_week, ->{ where("last_logged_in_at >= ?", 1.week.ago) }
end

class UserWeeklyReport
  def self.name
    "Weekly User Report"
  end

  def self.formatter
    ReportDataCsvCompiler
  end

  def self.data
    UserWeeklyReportData
  end

  def to_file
    File.write("/tmp/my_report")
  end
end

class ReportMailer
  attr_accessor :report, :recipient

  def intiialize(report:, recipient:)
    self.report = report
    self.recipient = recipient
  end

  def deliver!
    mail = Mail.new do
      from "jjbohn@gmail.com"
      to recipient
      subject report.name
    end

    mail.add_file(report.to_file)
    mail.deliver!
  end
end

class UserWeeklyReportData
  def to_data
    User.logged_in_this_week.map do |user|
      {
        id: user.id,
        username: user.username,
        last_logged_in_at: user.last_logged_in_at.strftime("%D"),
      }
    end
  end
end

# Then in you client code
ReportMailer.new(UserWeeklyReport, "jill@example.com")

Wow. That’s a lot more code. That said, the concerns are obvious and parts can easily be swapped out. Now that we’ve segregated responsibilities, a new feature such as uploading the report to dropbox is really easy. You just swap out the report delivery component, the ReportMailer in this case, with a new class and you’re all set. All the pieces become independent. They can evolve (and be tested) independently.

Conclusion

To wrap up, how far you decompose components based on responsibility all depends on the system you’re talking about. All systems are different and what is right for one may be overly complex for another. My rule of thumb is the following. The more likely a component or set of components are to change, the more I will split said components up by responsibility. In the end, be pragmatic about this and all programming “rules”.

Weekly Picks 2014-07-21

Holy crap. There were a lot of cool things I saw this week. I might not be as complete as usual in my descriptions, but trust me, click through and check everything out.

Andrew Sorensen OSCON 2014 Keynote: “The Concert Programmer”

https://www.youtube.com/watch?v=yY1FSsUV-8c

Answer Sorensen live coding a song (in a Lisp). This was really cool. Whil I don’t think it’s a great a seeing a really good musician play, it shows what tomorrow’s concerto might look like. Really fricking cool.

Rainbow Stream

https://github.com/DTVD/rainbowstream

This is the best terminal based Twitter client I have ever seen. You get real time streams, composition, search, etc. Plus images. Yes, images… from twitter… in the terminal.

Emacs & Vim — Martin Klepsch

http://www.martinklepsch.org/posts/emacs-and-vim.html

A four year vim user switches to emacs in with evil-mode. I’ve actually been considering this myself. I love vim for its key bindings, but envy emacs lisp.

CoreOS Stable Release

https://coreos.com/blog/stable-release/

Stable CoreOS!!! This might be my top pick from the week. I think CoreOS is going to be one of the most important web technologies over the next decade. Very excited.

Trello is now part of Trello, Inc.

http://blog.trello.com/trello-is-now-trello-inc/

Was waiting for this one. I think Trello is probably a much more recognizable name than Fog Creek.

mitmproxy: a man-in-the-middle proxy

http://mitmproxy.org/

Very cool little proxy. It lets you intercept, modify and most importantly replace and save HTTP/S traffic.

Blazing Fast HTML

http://elm-lang.org/blog/Blazing-Fast-Html.elm

I never used elm. To be honest, I don’t totally remember if I’d even heard of it. That said, it’s fast. Really fast. Thanks to it’s use of a virtual DOM. This is a concept that has been picking up steam lately. At this point. I think it’s a good idea.

mandelruby

https://github.com/nicklink483/mandelruby

A Mandelbrot fractal viewer written in Ruby and displays in the console. Enough said.

IBM: The Economic Value of Rapid Response Time

http://www.vm.ibm.com/devpages/jelliott/evrrt.html

A paper from 1982 describing the value of performance. It’s a short read with good info (and graphs).

Collection Pipeline

http://martinfowler.com/articles/collection-pipeline/

Marting Fowler documented a new pattern. It’s the idea of taking a collection and piping it through a series of commands.

An example from the command line:

1
grep -l ‘nosql’ bliki/entries/* | xargs wc -w | sort -nr

An example from Ruby:

1
2
3
4
some_articles
  .select{|a| a.tags.include?(:nosql)}
  .sort_by{|a| a.words}
  .take(3)

tmuxomatic

https://github.com/oxidane/tmuxomatic

Automated window layout and session management for tmux, with a simple definition file that is powerful and flexible. Aka: a tmux manager that let’s you do crazy things and write configuration files that can look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
window an_example         # <-- A new window begins like this, spaces in names are acceptable

HHHOOOOOVVVVXXXXAAAA      # <-- This is a windowgram, it defines shapes and positions of panes
HHHOOOOOTTTTXXXXAAAA      # <-- You make your own, any size and arrangement, 62 panes maximum
HHHqqqqqqqqkkkkkAAAA
jjjqqqqqqqqkkkkkAAAA
jjjqqqqqqqqkkkkk1234
jjjqqqqqqqqkkkkk5678
0000llllllllllaaaaaa
tmuxllllllllllaaaaaa

  foc                     # <-- Only 3 three-letter commands to remember: Focus, Directory, Run
  dir ~                   # <-- An unlinked command becomes the default for all following panes
a run figlet "a"          # <-- Here is a linked command to print a large "a" on pane a
q run figlet "q"
q foc
A run figlet "A"

Cr-A-Zy. And awesome.

The web request is a scary place

http://www.jeffield.net/2013/11/the-web-request-is-a-scary-place/

An interesting take instagram’s talk on how they scaled messaging.

An Illustrated Book of Bad Arguments

http://jjbohn.info/blog/2014/07/21/an-illustrated-book-of-bad-arguments/

This is my new book club book and looks great. I made a post specifically for this pick. Long story short, this books looks really great and you should read it with me. I’m posting my first notes on the book this week.

Top Level Telecommunications: New phones aboard Air Force One

http://electrospaces.blogspot.com/2014/07/new-phones-aboard-air-force-one.html

Phones. Air Force One. LEDs that change color to denote whether the line is secure. That’s stuff from the movies. No. It’s not. It’s on Air Force One. /Obama drops the mic, walks away. You just got served.

Virtual AGC

http://www.ibiblio.org/apollo/

Simulation of the Apoloo guidance systems. And source code. Of course I picked it.

Level-Up Your Machine Learning

http://metacademy.org/roadmaps/cjrd/level-up-your-ml

Want to learn machine learning? Don’t know where to start? This is a very well thought out guide containing a handful of recommendations on what book to read when.

DNS.watch

https://dns.watch/index

Fast, free and uncensored. (And my new DNS servers.) Seriously, I noticed the speed improvement.

Calling All Hackers: Help Us Build an Open Wireless Router

https://www.eff.org/deeplinks/2014/07/building-open-wireless-router

Want to help EFF build an open wireless router? Here’s your chance.

etcd: the Not-so-Secret Sauce in Google’s Kubernetes and Pivotal’s Cloud Foundry

http://www.datacenterknowledge.com/archives/2014/07/16/etcd-secret-sauce-googles-kubernetes-pivotals-cloud-foundry/

Again, I’m a CoreOS fanboy. etcd is essential. Configuration management for awesomeness.

How Instagram Feeds Work: Celery and RabbitMQ

http://blogs.vmware.com/vfabric/2013/04/how-instagram-feeds-work-celery-and-rabbitmq.html

Related to the earlier post on Instagram. Another good read on scaling big. Really big.

drawille-canvas

https://github.com/madbence/node-drawille-canvas

HTML5 canvas for your terminal. Another amazin terminal thing. I want d3 in my terminal now :).

Hacking POS Terminal for Fun and Non-profit

http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Hacking-POS-Terminal-for-Fun-and-Non-profit/ba-p/6540620

Restaurant POS systems have always been a secret obsession of mine. They look like hell and it seems like there is so much room for improvement. This is a cool article on what’s going on under the hood.

Escher in language—The algorithmic mirror

https://github.com/gocircuit/escher

Escher is a programming language for everything. It can naturally represent both process and data, while being simpler than a calculator grammar.

That’s what they say. I gave it a really quick look, I have no idea what’s going on. I think it’s mostly because I just looked at the graphs. I’ll give it another look soon to see if it’s something that I can use. Whatever it is, it looks epic.

Elixir Data Types

I’ve been playing with Elixir lately and wanted to persist a few things that I thought were interesting about some of it’s core data types. It’s very basic, but if you’re just getting started with Elixir like I am, you might find these notes interesting.

Elixir has a few of the basic types you’d expect to see in any other language. For example, Elixir has integers, floats and strings. Besides those types, there are atoms (aka. symbols), lists and tuples.

Atoms look just like symbols in Ruby. They are constants whose name is also it’s value.

1
2
3
4
5
6
iex> :foo
:foo
iex> :foo == :bar
false
iex> is_atom(:foo)
true

Another interesting note is that booleans are just atoms.

1
2
3
4
iex> is_atom(true)
true
iex> false == :false
true

Lists in Elixir are linked lists. A linked list is data structure in which each node contains a reference to the next node.

Linked
list

An Illustrated Book of Bad Arguments

Book Cover

I’ve been looking for a short read to kick off book club with and today I found what I think is just the right book. Here’s the description from Amazon:

Have you read (or stumbled into) one too many irrational online debates? Ali Almossawi certainly had, so he wrote An Illustrated Book of Bad Arguments! This handy guide is here to bring the internet age a much-needed dose of old-school logic (really old-school, a la Aristotle).

Here are cogent explanations of the straw man fallacy, the slippery slope argument, the ad hominem attack, and other common attempts at reasoning that actually fall short—plus a beautifully drawn menagerie of animals who (adorably) commit every logical faux pas. Rabbit thinks a strange light in the sky must be a UFO because no one can prove otherwise (the appeal to ignorance). And Lion doesn’t believe that gas emissions harm the planet because, if that were true, he wouldn’t like the result (the argument from consequences).

Once you learn to recognize these abuses of reason, they start to crop up everywhere from congressional debate to YouTube comments—which makes this geek-chic book a must for anyone in the habit of holding opinions. It’s the antidote to fuzzy thinking, with furry animals!

Yup. That sounds awesome. I had a brief look at it on their website too and it looks great. As of today, the book is still on pre-order (yes, I’ve already bought my copy because the illustrations look awesome). It comes out August 26th, 2014 in hardcover, but you read the book in its entirety on the “An Illustrated Book of Bad Arguments” website for free!

Installing Ruby with rbenv

The need for multiple versions of Ruby

As you work on different Ruby project over time, you’ll find that different projects projects require different versions of Ruby. To handle this, a few tools have been created. My personal favorite is rbenv. I recommend all rubyists use it even if you’re just starting out. If makes installing new versions, tying certain versions to certain projects, and much more a breeze.

rbenv vs rvm

Besides rbenv, there is another ruby version manager that has been out for some time and at one point, was the de facto standard. That tool was RVM. RVM is still fairly popular although it’s popularity is waning in favor of rbenv. Why is rbenv more popular? Because it does less. Programmers generally like software that does one thing and does it well. In terms of managing multiple versions of Ruby, rbenv just does less. More specifically, from the rbenv wiki:

rbenv does…

  • Provide support for specifying application-specific Ruby versions.
  • Let you change the global Ruby version on a per-user basis.
  • Allow you to override the Ruby version with an environment variable.

In contrast with RVM, rbenv does not…

  • Need to be loaded into your shell. Instead, rbenv’s shim approach works by adding a directory to your $PATH.
  • Override shell commands like cd or require prompt hacks. That’s dangerous and error-prone.
  • Have a configuration file. There’s nothing to configure except which version of Ruby you want to use.
  • Install Ruby. You can build and install Ruby yourself, or use ruby-build to automate the process.
  • Manage gemsets. Bundler is a better way to manage application dependencies. If you have projects that are not yet using Bundler you can install the rbenv-gemset plugin.
  • Require changes to Ruby libraries for compatibility. The simplicity of rbenv means as long as it’s in your $PATH, nothing else needs to know about it.

Installing rbenv

If you’re on a Mac, the recommended way to install rbenv is via Homebrew. If you don’t have Homebrew yet, I highly recommend it.

To install rbenv via Homebrew, just run:

1
brew install rbenv ruby-build

Now that you have rbenv installed, you need to add a line to your profile so everything loads up correctly when you start your terminal. The location of your profile varies depending on how your system is set up, but it will typically be either ~/.profile, ~/.bash_profile, or ~/.bashrc. If you’re using Zsh, it will probably be ~/.zshrc. Add the following line to your profile:

1
eval “$(rbenv init -)”

To test that it works, run:

1
type rbenv

You should get something like the following back if it worked:

1
rbenv is a shell function

Installing a new ruby with rbenv

Awesome, you should now have rbenv installed and you’re ready to roll. Well, almost ready. rbenv by itself doesn’t do much good. Let’s add a ruby version. As of this writing, the latest version of Ruby is 2.1.2. Pro tip: you can view all versions available to rbenv by running the follow from your terminal:

1
rbenv install -l

To install Ruby 2.1.2, run the following:

1
rbenv install 2.1.2

And, if you want to make that version your default global version, you can run:

1
rbenv global 2.1.2

Wrapping up

That’s it! I hope this helps you get set up with rbenv. There are a few other things that you’ll be able to do, such as installing alternate versions of Ruby (like Rubinius). If you want to learn more about rbenv, check out the README on github.