loading...

TPP Topic topic-30: Transforming Programming

steadbytes profile image Ben Steadman ・3 min read

This post originally appeared on steadbytes.com

See the first post in The Pragmatic Programmer 20th Anniversary Edition series for an introduction.

Exercise 21

Can you express the following requirements as a top-level transformation? That is, for each, identify the input and the output.

  1. Shipping and sales tax are added to an order
  2. Your application loads configuration information from a named file
  3. Someone logs in to a web application
  1. initial_order -> final_order
    • initial_order represents the unprocessed data of an order - e.g. the cost and quantity of each item.
    • final_order represents an order that has been processed/transformed into a shippable state with shipping and sales tax calculated and included appropriately.
  2. config_file_name -> config
    • The configuration file is read and it's contents are transformed into a data structure to be used for configuring the application.
  3. user_credentials -> user_session
    • The user provides their login credentials (username/password, OAuth token e.t.c) which are processed to grant access to the web application.

Exercise 22

You’ve identified the need to validate and convert an input field from a string into an integer between 18 and 150. The overall transformation is described by

field contents as string
   → [validate & convert]
       → {:ok, value} | {:error, reason}

Write the individual transformations that make up validate & convert

field contents as string
    -> [convert to integer]
    -> [18 <= value <= 150]
        -> {:ok, value} | {:error, "value not 18 <= value <= 150"}

Exercise 23

In Language X Doesn't Have Pipelines, on page 153 we wrote:

const content = File.read(file_name);
const lines = find_matching_lines(content, pattern);
const result = truncate_lines(lines);

Many people write OO code by chaining together method calls, and might be
tempted to write this as something like:

const result = content_of(file_name)
  .find_matching_lines(pattern)
  .truncate_lines();

What’s the difference between these two pieces of code? Which do you think
we prefer?

The second piece of code uses a fluent interface, whereas the first 'chains' isolated functions together by explicitly passing the result one function call to the next. The difference here is that each function call in the fluent interface is a method on the object returned by the previous call; the object returned by find_matching_lines must implement a truncate_lines method. This introduces tight coupling to the 'pipeline' code and the objects used within it. Changing the pipeline behaviour is more difficult as one needs to decide which object a given transformation should be attached to. For example, say the requirements change such that the output must all be lower case. In the first piece of code, this is trivial:

const content = File.read(file_name);
const lines = find_matching_lines(content, pattern);
const lower_case_lines = to_lower_case(lines);
const result = truncate_lines(lower_case_lines);

In the second piece of code however a decision needs to be made as to where the to_lower_case functionality should be implemented: perhaps it should be on the object returned by File.read, or maybe on the object returned by find_matching_lines. Depending on what those objects actually represent it may not make semantic sense to have a to_lower_case method in the general case. Furthermore, there is the possibility of breaking other code which uses the changed object.

I'm 100% certain that the authors prefer the first piece of code 😄

Discussion

pic
Editor guide