2019-09-15 20:56

Teenage Dreams About the Future

As a teenager I was hoping and dreaming about a future full of exciting technology. A while ago I ran into a couple of drawings I made in 1983. The drawings show the technology I was hoping for would become reality. To place the drawings in perspective: I was 15 years old at that time, and my only experience with a computer at that time was a Commodore PET.

A quick recap on 1983: Ronald Reagan was the US president, Lotus 1-2-3 was released, the final episode of MASH aired, IBM released the PC XT, the Space Shuttle Challenger was launched on its first flight, a Korean Air Lines plane was shot down in Soviet airspace, a worldwide nuclear war was averted by Stanislav Petrov, US and French army barracks were bombed, and Amy Winehouse was born.

But back to my drawings. I don't remember what inspired me to create them. It must have been a mix of stuff I learned by reading Byte Magazine and Communications of the ACM (my dad brought these home from his work), and a desire for better computing hardware than what the 80s had to offer.

Drawing #1

This is the drawing I like best 36 years later.

It depicts:

  • A home communication device, much like Alexa, Google Home, or Apple's HomePod.
  • A flat panel color display including a computer (like an iMac).
  • An Apple Watch-like device.
  • A copier and fax, including a stylus-enabled surface.
  • A data storage facility using solid state memory cards.
  • A fiber optic connection.

Devices 1983

Note: captions are in Dutch

There's a lot going on so I'll describe all parts starting from the top left corner going clockwise.

At the left side of the drawing you see this flexible, accordion-like tube. It's called the "Communicator". Between brackets it says "telefoon" (phone).

Observations:

  • The top ("bovenkant") sports a large button. The caption says "Press button, speak as much information, [you'll be put in] contact". Translated to today: a voice recognition assistant listens, analyses what you said, and tries to put you into contact with the right person.
  • The red ring encircling the top of the tube emits light to announce incoming calls.
  • The tube is flexible in the sense that it can be made shorter and longer ("in- en uitschuifbaar"), bent, and rotated ("draaibaar").
  • It has a volume control slider.
  • It has four color-coded connectors which connect to the display, "databank", "copier & text hand writing device", and a fiber optic connection ("glasvezelverbinding").

Fun fact: the rural village where I live, located in the north of The Netherlands, will finally get a fiber network in 2020.

To the right of the Communicator you see a color display monitor:

  • The display has a square aspect ratio. Apparently I liked the aesthetics of it but it hasn't gained much popularity.
  • It looks quite small (15"?) for today's display sizes but remember that back then most monitors were only around 13".
  • It is probably a flat panel monitor. Although it's not visible on the drawing, the thin, adjustable arm makes it look like the display itself can't support a heavy cathode ray tube. The arm has three joints to adjust the height and angle of the display.
  • Based on the fact that the rest of the drawing does not contain an actual computer-like device, I assume that the display itself contains the computer that drives the display.

Around 1982 the first, small LCD displays were launched measuring a couple of inches. I must have extrapolated this fact to this larger display I envisioned.

To the right of the display is an odd, colorful keyboard.

  • It sports a weird, non-QWERTY keyboard.
  • It has two ports (display and databank?).
  • It has colored keys, something you see on keyboards used by video editors nowadays.

Underneath the keyboard you see a digital watch and what is probably a charging station.

  • It can make sound ("bliep" means beep in Dutch) and it has a small red light.
  • It looks like the charging station has a speaker. Perhaps I envisioned taking calls while not wearing the watch?
  • The word "polsmelder" (wrist notifier) seems to indicate it could vibrate to notify the wearer about an incoming call.

I am not a fan of rectangular clock faces for analog watches but I suspect I gave this watch a rectangular face (with rounded corners though) because most electronic watches had this shape in the 80s.

Below the watch is a rather vague apparatus, called the "electronic text writer".

  • It has a lightpen, a device which was actually in use during the 60-80s era. The pen is attached to the device with a coiled wire.
  • The port at the left side (indicated by the green arrow) hints at a connection between the Communicator (left) and this electronic text writer.
  • The front shows a narrow, horizontal slit with "A4" printed below. The caption underneath says "Copying device, sender of text and image[s]". At the right side of the front panel there's a small "control panel", featuring an on/off switch, an LED, and another undefined control.

Apparently I'd envisioned that this device should be capable of transmitting handwritten notes, as well as scanned images and texts. Whether the surface on which you had to "write" using the lightpen also acted as a display of (digital) images is unknown, although the orange handwritten words "electronic text writer" seem to hint at a digital display and not only a lightpen-sensitive surface.

To the left of the text writer you see the "databank":

  • The black square-with-red-arrow contains the word "chip". Underneath it says, translated from Dutch, "chip card able to hold much information".
  • The device itself has 2 x 5 card readers, each reader having small red and green lights.
  • The databank can be connected to the other devices using a cable.

Drawing 2

I also drew the Communicator device in "rainbow colors":

Devices 1983

36 years later

I am glad I saved these drawings because it's fun to see everything I dreamt of became reality, sort of.

Permalink — Comments or kudos? Find me on Twitter.

Are you a project or product manager? Ship better software with Releasewise!


2019-09-11 20:39

A Better Way of Working With URIs in Ruby

In this post I discuss the Addressable gem and the ways in which it improves dealing with URIs in Ruby code. If you find the term URI confusing then I recommend reading this article first.

Before diving into the strengths of Addressable, let's look at a typical code example:

class Invoice
  BASE_URI = "https://api.example.com"
  INVOICE_URI = "#{BASE_URI}/customers/%<customer_id>i/invoices/%<invoice_id>i"

  def self.uri(customer_id:, invoice_id:, format: nil)
    uri = INVOICE_URI % { customer_id: customer_id, invoice_id: invoice_id }
    uri += ".#{format}" if format
    uri
  end
end

Using Addressable it could look like this:

class Invoice
  BASE_URI = "https://api.example.com"
  INVOICE_URI = "#{BASE_URI}/customers/{customer_id}/invoices/{invoice_id}{.format}"

  def self.uri(customer_id:, invoice_id:, format: nil)
    Addressable::Template.new(INVOICE_URI).expand({
      customer_id: customer_id,
      invoice_id: invoice_id,
      format: format
    })
  end
end

If we run both examples the results will be the same:

https://api.example.com/customers/1/invoices/2
https://api.example.com/customers/1/invoices/2.json

While you could argue that the example using Addressable consists of a few more lines, the benefit is that you no longer need a condition to deal with format. Additionally, we don't have to do any string concatenation which is a bit of a code smell.

Before we continue, let's mention the two foundations on which Addressable rests:

  1. IRI (Internationalized Resource Identifier)
  2. URI Template

IRI deals with URIs like iñtërnâtiônàlizætiøn.com i.e. URIs containing non-ASCII characters.

A URI template allows you to define a URI based on variables, which are expanded based on certain rules. RFC 6570 describes four so-called "template levels", all of which are supported by Addressable.

Template Level 1

This is the simplest level, which the RFC describes as:

[...] most template processors implemented prior to this specification have only implemented the default expression type, we refer to these as Level 1 templates.

Example:

template = Addressable::Template.new("https://www.example.com/{user}/{resource}")

template.expand({ user: 'erik', resource: 'archived documents' })

#=> https://www.example.com/erik/archived%20documents

Notice that Addressable properly escapes the space between archived and documents.

Template Level 2

RFC 6570 states about level 2:

Level 2 templates add the plus ("+") operator, for expansion of values that are allowed to include reserved URI characters (Section 1.5), and the crosshatch ("#") operator for expansion of fragment identifiers.

template = Addressable::Template.new("https://www.example.com/{+department}/people")

template.expand({ department: 'technology/r&d' })

#=> https://www.example.com/technology/r&d/people

The + in {+department} instructs the template to retain reserved URI characters instead of encoding them. Without the + the result would be https://www.example.com/technology%2Fr%26d/people.

Level 2 has one more trick up its sleeve namely fragments:

template = Addressable::Template.new("https://www.example.com/{+department}/people{#person}")

template.expand({ department: 'technology/r&d', person: 'erik' })

#=> https://www.example.com/technology/r&d/people#erik

Template Level 3

Level 3 turns it up a notch:

Level 3 templates allow multiple variables per expression, each separated by a comma, and add more complex operators for dot-prefixed labels, slash-prefixed path segments, semicolon-prefixed path parameters, and the form-style construction of a query syntax consisting of name=value pairs that are separated by an ampersand character.

String expansion with multiple variables:

template = Addressable::Template.new("https://www.example.com/map?{lat,long}")

template.expand({ lat: 37.384, long: -122.264 })

#=> https://www.example.com/map?37.384,-122.264

Reserved expansion with multiple variables:

template = Addressable::Template.new("https://www.example.com/{+department,floor}")

template.expand({ department: 'technology/r&d', floor: 1 })

#=> https://www.example.com/technology/r&d,1

Fragment expansion with multiple variables:

template = Addressable::Template.new("https://www.example.com/people{#id,person}")

template.expand({ id: 1001, person: 'erik' })

#=> https://www.example.com/people#1001,erik

Label expansion, dot-prefixed:

template = Addressable::Template.new("https://www.example.com/versions/v{.major,minor,build}")

template.expand({ major: 1, minor: 2, build: 1103 })

#=> https://www.example.com/versions/v.1.2.1103

Path segments, slash-prefixed:

template = Addressable::Template.new("https://www.example.com{/path,subpath}")

template.expand({ path: 'legal', subpath: 'terms-of-service' })

#=> https://www.example.com/legal/terms-of-service

Path-style parameters, semicolon-prefixed:

template = Addressable::Template.new("https://www.example.com/action?op=crop{;x,y,w,h}")

template.expand({ x: 0, y: 20, w: 256, h: 256 })

#=> https://www.example.com/action?op=crop;x=0;y=20;w=256;h=256

Form-style query, ampersand-separated:

template = Addressable::Template.new("https://www.example.com/action{?op,x,y,w,h}")

template.expand({ op: 'crop', x: 0, y: 20, w: 256, h: 256 })

#=> https://www.example.com/action?op=crop&x=0&y=20&w=256&h=256

Form-style query continuation:

template = Addressable::Template.new("https://www.example.com/action?op=crop{&x,y,w,h}")

template.expand({ x: 0, y: 20, w: 256, h: 256 })

#=> https://www.example.com/action?op=crop&x=0&y=20&w=256&h=256

Template Level 4

Finally, Level 4 templates add value modifiers as an optional suffix to each variable name. A prefix modifier (":") indicates that only a limited number of characters from the beginning of the value are used by the expansion (Section 2.4.1). An explode ("*") modifier indicates that the variable is to be treated as a composite value, consisting of either a list of names or an associative array of (name, value) pairs, that is expanded as if each member were a separate variable (Section 2.4.2).

String expansion with value modifiers:

template = Addressable::Template.new("https://www.example.com/{user:1}/{user}/{resource}")

template.expand({ user: 'erik', resource: 'archived documents' })

#=> https://www.example.com/e/erik/archived%20documents

Notice the /e/ path segment.

Explode (*) modifier examples:

template = Addressable::Template.new("https://www.example.com/map?{coords*}")

template.expand({ coords: [37.384, -122.264] })

#=> https://www.example.com/map?37.384,-122.264
template = Addressable::Template.new("https://www.example.com/map?{coords*}")

template.expand({ coords: { lat: 37.384, long: -122.264 } })

#=> https://www.example.com/map?lat=37.384,long=-122.264

Parsing

Besides creating URIs, Addressable can also be used to parse URIs.

Suppose you have to deal with UTM parameters:

template = Addressable::Template.new("http://{host}{/segments*}/{?utm_source,utm_medium}{#fragment}")

uri = Addressable::URI.parse("http://example.com/a/b/c/?utm_source=1&utm_medium=2#preface")

template.extract(uri)

#=> {"host"=>"example.com", "segments"=>["a", "b", "c"], "utm_source"=>"1", "utm_medium"=>"2", "fragment"=>"preface"}

For other examples see Addressable's readme. Also check out its tests, they're easy to read.

I am now using Addressable whenever I have to craft or parse URIs. Let me know if you spot any errors or omissions.

Permalink — Comments or kudos? Find me on Twitter.

Are you a project or product manager? Ship better software with Releasewise!


2019-09-02 14:55

Writing Better Ruby (Part 2)

This is part 2 of a series of blog posts on writing better Ruby. The first installment can be found here.

1. count vs size

Don’t use count as a substitute for size. For Enumerable objects other than Array it will iterate the entire collection in order to determine its size – style guide

The article gives these examples:

# bad
some_hash.count

# good
some_hash.size

I created a benchmark to check how much slower count is (spoiler alert: crazy slow!).

2. Hash#fetch defaults

Introduce default values for hash keys via Hash#fetch as opposed to using custom logic – style guide

The article provides this example:

batman = { name: 'Bruce Wayne', is_evil: false }

# bad - if we just use || operator with falsy value we won't get the expected result
batman[:is_evil] || true # => true

# good - fetch works correctly with falsy values
batman.fetch(:is_evil, true) # => false

The batman[:is_evil] || true statement yields true because false OR true equals true (simple Boolean logic). Use fetch() to get the actual value of :is_evil, or true if it's nil.

3. Hash#fetch

Use Hash#fetch when dealing with hash keys that should be present – style guide

A benefit of fetch is that it raises KeyError on non-existing keys. I find this particularly useful for ENV variables e.g. in Rails apps because an invalid fetch key will crash the app making it very clear that's something's gone awry.

Suppose your ".env" file contains STRIPE_PUBLIC_KEY=pk_test_123

# bad - using the wrong variable name returns nil, making debugging harder
stripe_public_key = Rails.ENV['STRIPE-PUBLIC-KEY']

# good - using fetch raises `KeyError`
stripe_public_key = Rails.ENV.fetch('STRIPE-PUBLIC-KEY')

4. Hash#each

Use Hash#each_key instead of Hash#keys.each and Hash#each_value instead of Hash#values.each – style guide

The reason for this advice is performance since .keys and .values create temporary arrays, while the each_* methods return laze enumerators. See this benchmark for a performance comparison between arrays and enumerators on strings and symbols.

5. Set vs Array

Use Set instead of Array when dealing with unique elements. Set implements a collection of unordered values with no duplicates. This is a hybrid of Array's intuitive inter-operation facilities and Hash's fast lookup – style guide

Since the article doesn't provide additional details, I've tried to sum up the differences between Set and Array:

  • A Set can only contain unique items. This enables optimizations to its internal list operations, making it a lot faster for some operations. For instance include? can be much faster when operating on a Set versus an Array.
  • As opposed to Array, a Set is an unordered list of values. Again, this enables Set to apply optimizations to the way it handles the list internally, making it faster than arrays. However, just as with Hashes prior to Ruby 1.9, if your code relies on the insertion order then you can't use Set.
  • Set provides powerful mathematical set operations such as union, intersection, and subtraction. It also offers a way to test for equality of two sets regardless the position of the values.

6. Inline Annotations

In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule – style guide

The article lists TODO, FIXME, OPTIMIZE, HACK, and REVIEW as suggested annotations. I only use the first two but I can see the benefit of using all five.

Note that Rails' notes command only scans for FIXME, TODO, and OPTIMIZE. If you want to scan for e.g. HACK use rails notes:custom ANNOTATION=HACK (or grep -R "# *HACK" *). Edit: Rails 6 now offers an annotations argument, see https://blog.saeloun.com/2019/09/03/rails-notes-command-upgrades-in-rails-6.html#rails-6.

7. Class and self

When class (or module) methods call other such methods, omit the use of a leading self or own name followed by a . when calling other such methods. This is often seen in "service classes" or other similar concepts where a class is treated as though it were a function. This convention tends to reduce repetitive boilerplate in such classes – style guide

I've made this mistake a lot in the past, writing e.g. code like this:

# bad
def profile_unfinished?
  self.email_address.blank? ||  self.name.blank?
end

# good
def profile_unfinished?
  email_address.blank? ||  name.blank?
end

8. module_function

Prefer the use of module_function over extend self when you want to turn a module’s instance methods into class methods – style guide

Frankly, I am on the fence over this one. I tend to follow this advice because standards are good even if you don't agree with every individual rule. See this article for a discussion whether extend self is an anti-pattern.

The advice made me revisit all the ways (I know of) to create class methods:

Method 1: using extend

module Mod
  def mod
    "hello"
  end
end

class Test
  extend Mod
end

Test.mod # "Hello"

Method 2: using module_function

module Mod
  module_function
  def mod
    "Hello"
  end
end

class Test
  def self.mod
    Mod.mod
  end
end

Test.mod # "Hello"

Method 3: using extend self

module Mod
  extend self
  def mod
    "Hello"
  end
end

class Test
  def self.mod
    Mod.mod
  end
end

Test.mod # "Hello"

Method 4: using self.{method_name}

module Mod
  def self.mod
    "Hello"
  end
end

class Test
  def self.mod
    Mod.mod
  end
end

Test.mod # "Hello"

Method 5: using class << self

module Mod
  class << self
    def mod
      "Hello"
    end
  end
end

class Test
  def self.mod
    Mod.mod
  end
end

Test.mod # "Hello"

9. Keyword Arguments vs Option Hashes

Use keyword arguments instead of option hashes – style guide

I've started to refactor old code to use keyword arguments, and it's really making a difference in terms of nicer, self-explaining code.

For example:

data = TwoFactor.scannable_png(@user.account, @2fa_provisioning)

Compare this to:

data = TwoFactor.scannable_png(email_address: @user.account, uri: @2fa_provisioning)

With a single glance you know that @user.account contains an email address and that @2fa_provisioning apparently uses to_s to return the provisioning URI (which is OK for domain objects).

10. No Exceptional Flows

Don’t use exceptions for flow of control – style guide

I've definitely violated this rule, especially in IO-based code where it's "easier" (okay, lazy) to catch exceptions than to write conditions to deal with expected and unexpected situations.

Example of "bad code":

begin
  filepath = File.join(self.config.working_directory, self.config.settings.file_matter.blog_manifest_file)
  manifest = File.read(filepath)
rescue
  puts("Can't find blog manifest '#{filepath}'")
  exit
end

Rewritten to omit the exceptional flow:

filepath = File.join(self.config.working_directory, self.config.settings.file_matter.blog_manifest_file)
path = Pathname.new(filepath)
raise "Can't find blog manifest '#{filepath}'" unless path.exist?
manifest = File.read(filepath)

Again, that's a wrap

In an upcoming third installment I will discuss the next 10 mistakes I often make. The first installment can be found here.

Permalink — Comments or kudos? Find me on Twitter.

Are you a project or product manager? Ship better software with Releasewise!