# Topic 28 - 29 ###### tags: `The Pragmatic Programmer` [TOC] ## Topic 28. Decoupling > Show how to keep separate concepts separate, decreasing coupling. > > - *coupling*—the dependencies between bits of code. :::info **Tip 44: Decoupled Code Is Easier to Change** ::: In this section we’ll talk about - Train wrecks—chains of method calls - Globalization—the dangers of static things - Inheritance—why subclassing is dangerous Coupling can occur just about any time two pieces of code share something. Some of the symptoms of coupling: - Wacky dependencies between unrelated modules or libraries. - “Simple” changes to one module that propagate through unrelated modules in the system or break stuff elsewhere in the system. - Developers who are afraid to change code because they aren’t sure what might be affected. - Meetings where everyone has to attend because no one is sure who will be affected by a change. ### **TRAIN WRECKS** ```jsx! public void applyDiscount(customer, order_id, discount) { totals = customer .orders .find(order_id) .getTotals(); totals.grandTotal = totals.grandTotal - discount; totals.discount = discount; } // The business decides that no order can have a discount of more than 40% // Tip 45 Tell, Don’t Ask public void applyDiscount(customer, order_id, discount) { customer .orders .find(order_id) .getTotals() .applyDiscount(discount); } public void applyDiscount(customer, order_id, discount) { customer .findOrder(order_id) // we shouldn’t fetch its list of orders and search them. We should instead get the order we want directly from the customer .getTotals() .applyDiscount(discount) } public void applyDiscount(customer, order_id, discount) { customer .findOrder(order_id) // the outside world doesn't have to know that the implementation of an order uses a separate object to store its totals .applyDiscount(discount) } public void applyDiscount(customer, order_id, discount) { customer .applyDiscountToOrder(order_id) // can, but not need } ``` :::info **Tip 45: Tell, Don’t Ask** 1. This principle says that you shouldn’t make decisions based on the internal state of an object and then update that object. 2. TDA is not a law of nature; it’s just a pattern to help us recognize problems. ::: #### **The Law of Demeter** It was created to help developers on the Demeter Project keep their functions **cleaner** and **decoupled**. The LoD says that a method defined in a class C should only call: - Other instance methods in C - Its parameters - Methods in objects that it creates, both on the stack and in the heap - Global variables: - We now don’t like the “global variable” clause (for reasons we’ll go into in the next section). It’s difficult to use this in practice: it’s a little like having to parse a legal document whenever you call a method. However, the principle is still sound. We just recommend a somewhat simpler way of expressing almost the same thing: “Don’t Chain Method Calls”. :::info **Tip 46: Don’t Chain Method Calls** Try not to have more than one “.” when you access something. ::: ```jsx= // This is pretty poor style amount = customer.orders.last().totals().amount; // and so is this... orders = customer.orders; last = orders.last(); totals = last.totals(); amount = totals.amount; ``` - There’s a big **exception** to the one-dot rule: the rule doesn’t apply if the things you are chaining are **really, really unlikely to change**. - In practice: - Anything in your application should be considered likely to **change**. - Anything in a **third-party library** should be considered **volatile**. - Particularly if the maintainers of that library **are known to change APIs between releases**. - **Libraries** that come with the **language** are probably pretty **stable** #### **Chains and Pipelines** (In Topic 30, *Transforming Programming*, talk about composing functions into pipelines) - These pipelines transform data, passing it from one function to the next. - **This is not the same as a train wreck of method calls, as we are not relying on hidden implementation details.** - That’s not to say that pipelines don’t introduce some coupling: **they do**. - The **format** of the data returned by one function in a pipeline must be **compatible** with the format accepted by the next. ### **THE EVILS OF GLOBALIZATION** Globals couple code for many reasons. The most **obvious** is that **a change** to the implementation of the global potentially **affects all the code in the system**. In practice, of course, the impact is fairly **limited**; the **problem** really comes down to **knowing that you’ve found every place you need to change**. :::info **Tip 47: Avoid Global Data** Instead of coding `Config.log_level`, they now say `Config.log_level()` or `Config.getLogLevel()`. ::: This is **better**, because it means that **your global data has a bit of intelligence behind it**. - If you decide to **change the representation** of log levels, you can maintain **compatibility by mapping** between the new and old in the Config API. - But you still have **only the one set** of configuration data. #### **Global Data Includes External Resources** **Any mutable external resource** is global data. - If your application uses a **database**, **datastore**, **file system**, **service API**, and so on, it risks falling into the globalization trap. The solution is to make sure you always **wrap these resources behind code that you control**. :::info **Tip 48: If It’s Important Enough to Be Global, Wrap It in an API** ::: ### **INHERITANCE ADDS COUPLING** The misuse of subclassing. - subclass: a class inherits state and behavior from another class. (Topic 31, Inheritance Tax.) ### **AGAIN, IT’S ALL ABOUT CHANGE** - **Coupled code is hard to change**: **Alterations** in one place can have secondary **effects elsewhere** in the code, and often in **hard-to-find** places that only come to light a month later in production. - **Keeping your code shy**: Having **it only deal with things it directly knows about**, will help keep your applications decoupled, and that will make them more amenable to change. ## Topic 29. Juggling the Real World > Examine four different strategies to help manage and react to events—a critical aspect of modern software applications. ### **EVENT** How to write applications that respond to events, and adjust what they do based on those events Four strategies that help: 1. Finite State Machines 2. The Observer Pattern 3. Publish / Subscribe 4. Reactive Programming and Streams ### **FINITE STATE MACHINES (FSM)** **The Anatomy of a Pragmatic FSM** - A state machine is basically just a specification of **how to handle events**. - It consists of a set of states, one of which is the <span class="red">current</span> state. - For each <span class="green">state</span>, we list the <span class="blue">events</span> that are significant to that <span class="green">state</span>. - For each of those <span class="blue">events</span>, we define the <span class="green">new current state</span> of the system. - Example We may be receiving multipart messages from a websocket. 1. The first message is a header. 2. This is followed by any number of data messages, followed by a trailing message. This could be represented as an FSM like this: ![](https://i.imgur.com/5p1UADO.png) ```ruby! TRANSITIONS = { initial: {header: :reading}, reading: {data: :reading, trailer: :done}, } state = :initial while state != :done && state != :error msg = get_next_message() state = TRANSITIONS[state][msg.msg_type] || :error # implements the transitions between states end ``` A pure FSM, such as the one we were just looking at, is an event stream parser. Its only output is the final state. **FSM: Adding Actions + default transition** ```ruby TRANSITIONS = { # current new state action to take #--------------------------------------------------------- look_for_string: { '"' => [ :in_string, :start_new_string ], :default => [ :look_for_string, :ignore ], }, in_string: { '"' => [ :look_for_string, :finish_current_string ], '\\' => [ :copy_next_char, :add_current_to_string ], :default => [ :in_string, :add_current_to_string ], }, copy_next_char: { :default => [ :in_string, :add_current_to_string ], }, } state = :look_for_string result = [] while ch = STDIN.getc state, action = TRANSITIONS[state][ch] || TRANSITIONS[state][:default] case action when :ignore when :start_new_string result = [] when :add_current_to_string result << ch when :finish_current_string puts result.join end end ``` ### **THE OBSERVER PATTERN** In the observer pattern **(synchronously)** 1. we have a source of <span class="blue">events</span>, called the <span class="blue">**observable**</span> 2. a list of clients, the <span class="green">**observers**</span>, who are interested in those <span class="blue">events</span>. - Flow 1. An <span class="green">**observer**</span> registers its interest with the <span class="blue">**observable**</span>, typically by passing a reference to a <span class="orange">function</span> to be <span class="orange">called</span>. 2. Subsequently, when the <span class="blue">events</span> occurs, the <span class="blue">**observable**</span> iterates down its list of <span class="green">**observers**</span> and <span class="orange">calls</span> the <span class="orange">function</span> that each passed it. - The <span class="blue">event</span> is given as a parameter to that <span class="orange">call</span>. - Example The Terminator module is used to terminate the application. Before it does so, however, it notifies all its <span class="green">**observers**</span> that the application is going to exit. They (a.k.a. the <span class="green">**observers**</span>) might use this notification to tidy up temporary resources, commit data, and so on: ```ruby # event/observer.rb module Terminator # observers CALLBACKS = [] # observable def self.register(callback) CALLBACKS << callback end def self.exit(exit_status) CALLBACKS.each { |callback| callback.(exit_status) } exit!(exit_status) end end Terminator.register(-> (status) { puts "callback 1 sees #{status}" }) Terminator.register(-> (status) { puts "callback 2 sees #{status}" }) Terminator.exit(99) ``` ```ruby # $ ruby event/observer.rb callback 1 sees 99 callback 2 sees 99 ``` - Use case: user interface systems It is particularly prevalent in user interface systems, where the callbacks are used to inform the application that some interaction has occurred. - Problem The observer pattern has a problem: 1. Because each of the <span class="green">**observers**</span> has to register with the <span class="blue">**observable**</span>, it introduces **coupling**. 2. In addition, because in the typical implementation the callbacks are handled inline by the <span class="blue">**observable**</span>, **synchronously**, it can introduce **performance bottlenecks**. <br/> :::success This is solved by the next strategy, Publish/Subscribe. ::: ### **PUBLISH/SUBSCRIBE (pubsub)** Publish/Subscribe (pubsub) generalizes the **observer pattern**, at the same time **solving** the problems of **coupling** and **performance**. In the pubsub model (**asynchronous**) 1. we have <span class="blue">**publishers**</span> and <span class="green">**subscribers**</span>. These are connected via <span class="orange">channels</span>. 1. Every <span class="orange">channel</span> has a name. 2. The communication between the <span class="blue">**publisher**</span> and <span class="green">**subscriber**</span> is handled **outside** your code, and is potentially **asynchronous**. <span class="green">**subscribers**</span> register interest in one or more of these named <span class="orange">channels</span>, and <span class="blue">**publishers**</span> write <span class="blue">events</span> to them (a.k.a. <span class="orange">channels</span>). - Upside and Downside - Upside: Pubsub is a good technology for **decoupling** the handling of **asynchronous** events. e.g. It allows **code** to be **added** and **replaced**, potentially while the application is **running**, **without altering existing code**. - Downside: Pubsub can be **hard to see what is going on** in a system that uses pubsub heavily. e.g. You can’t look at a <span class="blue">**publisher**</span> and immediately see which <span class="green">**subscribers**</span> are involved with a particular message. - Use case: Pubsub is a great example of reducing coupling by abstracting up through a shared interface (the channel). However, it is still basically just a **message passing system**. :::success Creating systems that respond to combinations of events will need more than this, so let’s look at ways we can add a **time dimension to event processing**. ::: ### **REACTIVE PROGRAMMING, STREAMS, AND EVENTS** If you’ve ever used a spreadsheet, then you’ll be familiar with reactive programming. If a cell contains a formula which refers to a second cell, then updating that second cell causes the first to update as well. **The values react as the values they use change**. e.g. this kind of data-level reactivity: React and Vue.js - **Events and Streams** - **Events** - **Events** can also be used to trigger reactions in code, but it isn’t necessarily easy to plumb them in. - That’s where **streams** come in. - **Streams** - **Streams** let us treat **events** as if they were a collection of data. e.g. It’s as if we had a list of **events**, which got longer when new **events** arrive. - **De facto baseline for reactive event handling** The current de facto baseline for reactive event handling is defined on the site [http://reactivex.io](http://reactivex.io/), which defines a language-agnostic set of principles and documents some common implementations. e.g. RxJs library for JavaScript - Example (RxJs) - Takes two streams and zips them together: The result is a new stream where each element contains one item from the first input stream and one item from the other. 1. The first stream is simply a list of five animal names. 2. The second stream is more interesting: it’s an interval timer which generates an event every 500ms. Because the streams are zipped together, a result is only generated when data is available on both, and so our result stream only emits a value every half second: ```jsx import * as Observable from 'rxjs' import { logValues } from "../rxcommon/logger.js" let animals = Observable.of("ant", "bee", "cat", "dog", "elk") let ticker = Observable.interval(500) let combined = Observable.zip(animals, ticker) combined.subscribe(next => logValues(JSON.stringify(next))) /* "['ant',0]" "['bee',1]" "['cat',2]" "['dog',3]" "['elk',4]" */ ``` Notice the timestamps: we’re getting one event from the stream every 500ms. Each event contains a serial number (created by the interval observable) and the name of the next animal from the list. - Event streams are normally populated as events occur, which implies that the observables that populate them can **run in parallel**. ```jsx import * as Observable from 'rxjs' import { mergeMap } from 'rxjs/operators' import { ajax } from 'rxjs/ajax' import { logValues } from "../rxcommon/logger.js" let users = Observable.of(3, 2, 1) let result = users.pipe( mergeMap((user) => ajax.getJSON(`https://reqres.in/api/users/${user}`)) ) result.subscribe( resp => logValues(JSON.stringify(resp.data)), err => console.error(JSON.stringify(err)) ) /* "{'id':1,'email':'george.bluth@reqres.in','first_name':'George','last_name':'Bluth','avatar':'https://reqres.in/img/faces/1-image.jpg'}" "{'id':2,'email':'janet.weaver@reqres.in','first_name':'Janet','last_name':'Weaver','avatar':'https://reqres.in/img/faces/2-image.jpg'}" "{'id':3,'email':'emma.wong@reqres.in','first_name':'Emma','last_name':'Wong','avatar':'https://reqres.in/img/faces/3-image.jpg'}" */ /* "{'id':3,'email':'emma.wong@reqres.in','first_name':'Emma','last_name':'Wong','avatar':'https://reqres.in/img/faces/3-image.jpg'}" "{'id':1,'email':'george.bluth@reqres.in','first_name':'George','last_name':'Bluth','avatar':'https://reqres.in/img/faces/1-image.jpg'}" "{'id':2,'email':'janet.weaver@reqres.in','first_name':'Janet','last_name':'Weaver','avatar':'https://reqres.in/img/faces/2-image.jpg'}" */ /* "{'id':2,'email':'janet.weaver@reqres.in','first_name':'Janet','last_name':'Weaver','avatar':'https://reqres.in/img/faces/2-image.jpg'}" "{'id':1,'email':'george.bluth@reqres.in','first_name':'George','last_name':'Bluth','avatar':'https://reqres.in/img/faces/1-image.jpg'}" "{'id':3,'email':'emma.wong@reqres.in','first_name':'Emma','last_name':'Wong','avatar':'https://reqres.in/img/faces/3-image.jpg'}" */ ``` Look at the timestamps: the three requests, or three separate streams, were processed in parallel, The first to come back, for id 2, took 82ms, and the next two came back 50 and 51ms later. **Streams of Events Are Asynchronous Collections** In the previous example, our list of user IDs (in the observable users) was static. But it doesn’t have to be. All we have to do is to generate an observable event containing their user ID when their session is created, and use that observable instead of the static one. This is a very powerful abstraction: **we no longer need to think about time as being something we have to manage**. **Event streams unify synchronous and asynchronous processing behind a common, convenient API.** <style> .blue { color: blue; } .red { color: red; } .green { color: green; } .orange { color: orange; } </style>