Doing It Wrong

learn in public

Site Navigation

  • Home
  • Books
  • Work & Play

Site Search

The Pragmatic Programmer: Chapter 7

posted on January 10, 2024

While You Are Coding

Coding is an intensely creative act that happens through instincts / nonconscious thoughts and the active thoughtful application of principles.

Listen to Your Lizard Brain

The first trick is to notice that it’s happening; then work out why. You may not be able to, but try to crystallize it into something solid that you can address. Let your instincts contribute to your performance.

  • fear of the blank page is usually caused by
  • some kind of important doubt lurking below perception
  • you might be afraid you’ll make a mistake
  • how to figure out what’s lurking below perception?
  • let your subconscious brain process the problem by doing something else away from the keyboard
  • try externalizing the issue: rubber duck, doodle
  • maybe even prototype around the problem space giving you disease; this context switch can help you work through it
  • in other people’s code, notice what give you pause so that you can lean from the person who wrote it

Filed Under: Development Tagged With: Book, Notes

Form-Associated Custom Elements (FACE)

posted on January 8, 2024

I was using the static property formAssociated on some Lit -based web components I’m writing for a data-subsetting form at work. I’m bummed that it doesn’t click well with my brain. I get it, though—APIs are hard to design, and I can’t even imagine the level of skill needed to build the web component spec. I just wish it had been an attribute on a form input, not a static property on the web component class.

This is how you define a form-associated web component that can set its value on the parent form, using Lit element:

class SubsetterPart extends LitElement {
  static formAssociated = true; // (1)

  /*
    Look, you do your private class fields however you want
    when you're at home. In this house we use proper native
    private class fields.
  */
  #internals;

  #name = 'subsetter-part'

  constructor() {
    super();

    this.#internals = this.attachInternals(); // (2)
  }

  connectedCallback() {
    super.connectedCallback();

    this.setAttribute('name', this.#name) // (3)
  }

  @state();
  state = {};

  #handleChange(event) {
    const { value } = event.target

    this.#internals.setFormValue(value) // (4)

    /* set some state and stuff... */
  }

  /* The rest of your class stuff...including the form <input>s */
Code language: TypeScript (typescript)

This associates your custom element with a parent form (1), lets you access some shared parent state (2), and adds a name onto the host element (shadow host? whatever…the web component, itself) so that the parent form’s submit handler can read it from the FormData (3), then set’s that value through a method available on the shared state (4). A quick aside: learning enough about FACE web components to be able to type that sentence cost me three days (they felt so unproductive).

But why? In this <subsetter-part> custom element, why would I want the host to control whether it associates itself with a form? Well, that might be useful if you were making a web component that wanted to itself be thin wrapper around a form control—think maybe a custom temporal picker element that might be used to choose a temporal point [start] for subsetting data.

  /* The rest of your class stuff...including the form <input>s. */

  #validity = {
   /* Some instance state about what's a valid start date, end date. */
  }

  render() {
    return html`
      <label>
        <slot name="label">choose a date</slot>
        <input
          .value=${this.state.date}
          @change=${this.#handleChange}
          name="date"
          type="date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
        >
      </label>
    `
  }
Code language: TypeScript (typescript)

If you’re vision of web components includes that use case, I’m happy. We share that vision. But if your vision of web components ends at that, I’m bummed.

What if I want to build a form control that is itself made up of a few form controls? Like what if I wanted to make a custom element that allowed me to select a temporal range [start, end] for that same data subsetting task?

I’m going to code this and you’re maybe going to have the objection that I could have built this with a series of the same “thin wrapper around a form control” components that I just wrote plus a container component, and you’re right. It’s not that I can’t get this done with the current API, it’s that it doesn’t click with my brain. Anyway, a few form controls like this:

  /* The rest of your class stuff...including the form <input>s. */

  #validity = {
   /* Some instance state about what's a valid start date, end date. */
  }

  render() {
    return html`
      <label>
        <slot name="start-label">choose a start date</slot>
        <input
          .value=${this.state.startDate}
          @change=${this.#handleChange}
          type="start-date"
          name="start-date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
        >
      </label>

      <label>
        <slot name="end-label">choose an end date</slot>
        <input
          .value=${this.state.endDate}
          @change=${this.#handleChange}
          type="end-date"
          name="end-date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
        >
      </label>
    `
  }
Code language: TypeScript (typescript)

So, altogether like this:

class SubsetterPart extends LitElement {
  static formAssociated = true; // (1)

  #internals;

  #name = 'subsetter-part'

  constructor() {
    super();

    this.#internals = this.attachInternals(); // (2)
  }

  connectedCallback() {
    super.connectedCallback();

    this.setAttribute('name', this.#name) // (3)
  }

  @state();
  state = {};

  #handleChange(event) {
    const { value } = event.target

    this.#internals.setFormValue(value) // (4)

    /* set some state and stuff... */
  }

  #validity = {
   /* Some instance state about what's a valid start date, end date. */
  }

  render() {
    return html`
      <label>
        <slot name="start-label">choose a start date</slot>
        <input
          .value=${this.state.startDate}
          @change=${this.#handleChange}
          type="start-date"
          name="start-date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
        >
      </label>

      <label>
        <slot name="end-label">choose an end date</slot>
        <input
          .value=${this.state.endDate}
          @change=${this.#handleChange}
          type="end-date"
          name="end-date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
        >
      </label>
    `
  }
Code language: TypeScript (typescript)

Whereas if formAssociated had been an attribute, this is how I’d imagine it being used:

class SubsetterPart extends LitElement {
  @state();
  state = {};

  #validity = {
   /* Some instance state about what's a valid start date, end date. */
  }

  #handleChange = (event) => {
    /* Some of the most amazing change-handling you've seen, admit it. */
  }

  render() {
    return html`
      <label>
        <slot name="start-label">choose a start date</slot>
        <input
          .value=${this.state.startDate}
          @change=${this.#handleChange}
          type="start-date"
          name="start-date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
          formassociated
        >
      </label>

      <label>
        <slot name="end-label">choose an end date</slot>
        <input
          .value=${this.state.endDate}
          @change=${this.#handleChange}
          type="end-date"
          name="end-date"
          min=${this.validity.fromDate}
          max=${this.validity.toDate}
          formassociated
        >
      </label>
    `
  }
Code language: TypeScript (typescript)

In my magical greenfield world where such a thing is possible, the formassociated attribute would tell the parent form that this form control and some key associations should pierce through the Shadow DOM to a parent form. Maybe formassociated="some-form-id" could even be a thing, like form on a button, etc. Multiple levels of form -> Shadow DOM could have some common-sense use case like using the first available (but form attribute is a nice escape hatch). Because formassociated is on the input, any form-control-associated label can come along for the ride, too!

In the end, and like I was starting to say previously, I know this same thing could be build with the current API: I just need to chunk components differently. But that’s what I dislike! Sometimes a couple of form controls is just the right amount of atomicity. Sometimes I want to share that state and functionality in one class / custom element instead of two. And making everything a well-abstracted component takes time and boilerplate and more files. The hypothetical attribute formassociated lets me chunk my state / control my atomicity way better than the static property formAssociated.

Filed Under: Development Tagged With: Web Components

The Pragmatic Programmer: Chapter 6

posted on December 27, 2023

Concurrency

  • concurrency is when the execution of two or more pieces of code act as if they run at the same time: it is a software mechanism
  • parallelism is when they do run at the same time: it is a hardware concern
  • Concurrency requires that you run code in an environment that can switch execution between different parts of your code. This is often implemented using things like fibers, threads, and processes.
  • Almost any decent-sized codebase will need to handle concurrency: it’s just a real-world requirement.
  • temporal coupling happens when your code imposes a sequence of things that is not actually required to solve the problem at hand.
  • Shared state is the biggest liability once two things can happen at the same time.

Breaking Temporal Coupling

There are two aspects of time that are important to us: concurrency (things happening at the same time), and ordering (the relative positions of things in time). We should think about concurrency in our project by analyzing our workflow through an activity diagram.

  • drawn with rounded boxes for actions
  • arrow leaving an action leads to another action (A -> B means B must wait on A) or to a thick line called a synchronization bar
  • once all actions leading to a synchronization bar are complete, you can proceed along any arrows leaving it (to another action)
  • actions with no arrows leading in can be completed at any time.
  • this activity helps to identify activities that could be performed in parallel

Section Challenges

  • [ ] How many tasks do you perform in parallel when you get ready for work in the morning? Could you express this in a UML activity diagram? Can you find some way to get ready more quickly by increasing concurrency?

Shared State is Incorrect State

Scenarioizing servers in an restaurant selling pie (in a pie case) to their tables. Both see the last piece of pie and promise it to their table. One is disappointed.

  • The problem is not that two processes can write to the same memory; the problem is that neither process can guarantee that its view of that memory is consistent.
  • This is because fetching then updating the pie count is not an atomic operation: the underlying value can change in the middle.
  • Semaphores (a thing that only once process can own at a time) can solve this: only the server holding a plastic Leprechaun from the pie case can sell a pie.
  • trade-offs: this only works if all follow the convention
  • mitigation: move resource locking / semaphore handling into resource
  • multiple-resource transactions (pie and ice cream) should generally be a separate resource (so that you don’t have a server holding pie without ice cream)
  • non-transactional updates: concurrency problems aren’t restricted to writing to memory, but can pop up in files, databases, external services, etc.
  • whenever two or more instances of your code can access some resource at the same time, you have a potential for a problem
  • random failures are often concurrency issues
  • many languages have library support for mutexes (mutual exclusion), monitors, or semaphores
  • one could argue that functional languages (tendency to make all data immutable) make concurrency simpler, though they also run in the real world, subject to temporal restrictions, so you need to be aware of concurrency

Actors and Processes

Actors and processes offer a means to implement concurrency without the burden of synchronizing access to shared memory. Use actors for concurrency without shared state

  • an actor is an independent virtual processor with its own local (and private) state
  • each actor has a mailbox, whose messages get processed as soon as the actor is idle
  • there’s no single thing in control: nothing schedules what happens next, no orchestration of raw data to final output
  • the only state in the system is held in messages and the local, private state of the actor
  • all messages are one-way: there’s no built-in concept of replying
    • replies can be built by including a mailbox address in the message; make replying part of the message processing
  • processes each message to completion, and only processes one message at a time
  • a process is typically a more general-purpose virtual processor, often implemented by the operating system to facilitate concurrency

The diner / server scenario with actors:

  • the customer becomes hungry
  • they respond by asking the server for pie
  • the server asks the pie case for pie
  • if available, pie case sends pie to customer and notifies waiter to add to bill
  • if not available, pie case informs server

In all of the code for this, there’s no explicit concurrency handling, as there is no shared state. Erlang language and runtime are a great example of an actor implementation. Erlang calls actors processes, but they’re not regular operating system processes as described in this chapter’s notes. Also has a supervision system that manages process lifetimes.

Section Challenges

  • [ ] Do you currently have code that uses mutual exclusion to protect shared data? Why not try a prototype of the same code written using actors?
  • [ ] The actor code for the diner only supports ordering slices of pie. Extend it to let customers order pie a la mode, with separate agents managing the pie slices and the scoops of ice cream. Arrange things so that it handles the situation where one or the other runs out.

Blackboards

Blackboards provide a form of laissez faire concurrency, where the blackboard is the storage repository for independent processes, agents, actors, etc. They may be a good fit when writing an application with many sometimes independent and sometimes inter-dependent steps. Use them to coordinate workflows. Imagine writing an application to process loan applications.

  • responses (to credit inquiries, bank account balances, etc.) can arrive in any order.
  • data aggregation may be done by many different people, distributed across different time zones
  • some data gathering may be done automatically by other systems; data may arrive asynchronously
  • certain data may be dependent on other data (e.g., cannot start a car’s title search until the system has proof of ownership)
  • the arrival of new data raises new questions and policies; e.g., bad result from a credit check means stricture requirements for down payment

Messaging systems (think NATS, SQS, Kafka) can be like blackboards, since they offer message persistence and the ability to retrieve messages through pattern matching. Using blackboards comes with trade-offs:

  • harder to reason about business logic because everything is disconnected
  • observability can suffer unless you implement a system
  • agreeing on a communication format (or at least having a repository of different formats) requires work
  • more troublesome to deploy as there are more moving parts

Section Challenges

  • [ ] Exercise 24 Would a blackboard-style system be appropriate for the following applications? Why or why not?
  • Image processing: you’d like to have a number of parallel processes grab chunks of an image, process them, and put the completed chunk back.
  • Group calendaring: you’ve got people scattered across the globe, in different time zones, speaking different languages, trying to schedule a meeting.
  • Network monitoring tool: the system gathers performance statistics and collects trouble reports, which agenst use to look for trouble in the system.
  • [ ] Do you use blackboard systems in the real world—the message board by the refrigerator, or the big whiteboard at work? What makes them effective? Are messages ever posted with a consistent format? Does it matter?

Filed Under: Development Tagged With: Book, Notes

  • « Previous Page
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • …
  • 9
  • Next Page »

Profile Links

  • GitHub
  • Buy Me a Coffee?

Recent Posts

  • Understanding `satisfies`
  • Event Listeners
  • A Philosophy of Software Design
  • The Programmer’s Brain
  • Thoughts on Microservices

Recent Comments

No comments to show.

Archives

  • August 2026
  • May 2025
  • September 2024
  • July 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • December 2022
  • December 2021

Categories

  • Development