Categories
Uncategorized

Wizard of Birds

Young friends in a war-torn nation receive an urgent letter from their erstwhile schoolmate whose mother has vanished. When they arrive, they find their friend has also disappeared into an abandoned tower in the woods, inhabited by nothing but birds.

Do you like D&D adventures? How about Studio Ghibli movies? You might like this.

For this year’s one-shot I was inspired by a recent book of one-shots I received as a gift. I realized that a lot of the details I usually provide, like skill checks and DCs, are probably totally unnecessary. The DM can come up with these things on the fly. This is therefore the shortest one-shot document I’ve written yet!

Categories
Uncategorized

Warrior’s Tea

I went on a trip to hang out with my co-workers this February and ran a one-shot D&D game for them. Having just read the fabulous Six of Crows I was in the mood for a heist. Coincidentally, just a few weeks later, Wizards was going to release a whole book on the topic but since I didn’t have that I just came up with my own. Here it is, if you’d like to try running it yourself!

Categories
Uncategorized

Good coding practices

I recently had to give a talk on “Good coding practices” for some new developers at Automattic. At first I had no idea how to summarize such a vast and dynamic topic, but as I was working on a plan I started to see some ideas come up over and over again and I decided to turn my thoughts into a post.

Caveat lector: what makes a “good coding practice” is highly subjective and is also likely to change over time; this is my personal interpretation of good coding practices for a particular codebase at the time of writing. Use your own judgement always.

The format of this advice is designed to mimic the guidelines from Clean code PHP and Clean code JavaScript, which in general are quite good, but I don’t agree with all their suggestions and I think there’s some good advice left out. I also highly recommend The Art of Readable Code; it goes into much more detail about many of the concepts below.

What is good code?

Code should be written to minimize the time it would take for someone else to understand it.

The Art of Readable Code

To me, good code is explicit, semantic, and modular.

A common maxim in development is “explicit is better than implicit“. Explicit code makes no assumptions about what the reader knows. send_welcome_email_to_user() is an explicitly-named function. Implicit code does things that are implied or that you have to already know. If a function called get_price() also applies discounts, applying the discounts is an implicit action. What if we had get_price() and apply_discounts()? Explicit code is safer to use because it doesn’t do unexpected things. Some behavior will always be implied – a function is an abstraction and all abstractions are leaky – so keep in mind that this guideline is a gradient, not true-or-false.

Semantic code has meaning. A variable named data doesn’t tell us much about what it contains. A variable named timeSpent is ok because it describes a purpose. A variable named secondsSpent is better because it is pretty clear about what it contains. What’s the meaning of the variable open? Would it make more sense if it was isOpen? Naming well is probably the hardest thing in coding, so don’t beat yourself up but try to choose names for classes, functions, and variables that convey their purpose the best that you can. Good comments and clear spacing can also have a huge effect on the meaning of code. It’s normal to write code to solve a problem, but remember to then look at that code from the perspective of someone who has no idea of the problem or the context in which it exists.

Programs and their parts are written the way that humans think: as a sequential list of instructions. However, if you write all your code in one place, it becomes quite hard to remember what variables are used where, what conditions may be active, and how to make a change without breaking something else. Modular code is not tightly coupled to other code. Having a clear boundary between one function and another makes it possible to see the flow of a program quickly and incorporate changes with minimal risk.

Working with data

Use value objects rather than “smart” objects

It’s possible to divide objects into two general types: objects that “do” something, and objects that “are” something. For example, an object that represents the properties of a user is an object that “is” something; an object that sends an email to a user is an object that “does” something. Keeping these two responsibilities separate will produce code that is easier to reason about and is less bloated.

If a “user” object has a sendEmail method, then that method will be passed around everywhere, even if most places never need to send an email. It also means that anytime we need to change how an email is sent, we have to modify the user object, potentially causing problems for code that has nothing to do with emails.

An object that contains only data and no operations can be called a “value object” (technically we’d also need to be able to compare those objects by value instead of by reference, and we could write functions to do that if necessary). They are cheap to create and provide a semantic container for a certain set of values.

Bad

class User {
  public int $user_id;

  public function send_welcome_email(): void { /* ... */ }
}

Good

class User {
  public int $user_id;
}

class UserEmailer {
  public function send_welcome_email( User $user ): void { /* ... */ }
}

With User as a value object we prevent it from becoming bloated and make it easy to change. Constructor promotion and Readonly properties in PHP 8+ help this technique but be careful to avoid dynamic properties since they will be deprecated in PHP 8.2.

Depend on interfaces

An interface defines the structure or the API of a function, variable, or object. For a function, an interface defines what kind of data it accepts and returns. For a variable or object, an interface defines the data it holds and the methods it can perform.

When depending on an interface, we should generally depend only on the types that we need, although there are sometimes functions that need a very specific type of data.

In TypeScript:

Bad

function getUsername( user ) {
  // ... lots of code here
  return user.firstName;
}

getUserName() is pretty simple in this example, but imagine that it was 100 lines long. You’d need to read the whole function to understand type of data you have to pass in. We can use an interface to explain exactly what kind of data the function needs and what it returns. This is a guarantee that the function will operate a certain way.

Good

interface User {
  firstName: string;
}

function getUsername( user: User ): string {
  // ... lots of code here
  return user.firstName;
}

const myRegularUser = { firstName: 'john', theme: 'blue' };
const myAdminUser = { firstName: 'bob', dbKey: 12345, theme: 'green', accessLevel: 5 };
getUsername( myRegularUser );
getUsername( myAdminUser );

By just reading the first line of the function, we now know how to use it. Because we depend on an interface, we can pass any object into the function as long as it has a firstName property.

In PHP:

Bad

function get_username( $user ) {
  // ... lots of code here
  return $user->first_name;
}

This function is actually very flexible. It accepts any kind of object that has a $first_name property, but again there’s no way to know that without reading the function body. We can make its behavior more clear by adding types to the function definition. This is similar to what we did above with TypeScript, except that WordPressUser is a concrete type.

Good

class WordPressUser {
  public string $first_name;
  public int $wp_id;
  public int $access_level;
}

function getUsername( WordPressUser $user ): string {
  // ... lots of code here
  return $user->first_name;
}

By adding types we now know how the function is intended to work, and we’ve provided guarantees, but by using a concrete typehint we’ve actually decreased what the function can do in the process. The function has the ability to operate on any object that has a $first_name property but we’ve limited it to only instances of the WordPressUser class. In some cases that’s fine (maybe we only want to support that class), but we can do better.

Better

interface HasName {
  public string $first_name;
}

class WordPressUser implements HasName {
  public string $first_name;
  public int $wp_id;
  public int $access_level;
}

class TumblrUser implements HasName {
  public string $first_name;
  public int $tumblr_id;
  public string $site_name;
}

function getUsername( HasName $user ): string {
  // ... lots of code here
  return $user->first_name;
}

By using an interface as the typehint, we now show that the function will work for any object that has a name.

For code that needs to support older versions of PHP, we can use PHPDoc types! These can enforced by static analysis tools like psalm or phpstan.

Don’t return data without a type

When writing functions we often need to pass around data in temporary forms. It’s tempting to do this by creating an anonymous object, but this means that anyone who wants to use that data must read where it is created in order to know what properties it has. In addition, if the data ever changes, without an interface the developer would need to manually find every place that uses that data to make sure it doesn’t break. Prefer using explicit interfaces even for temporary data.

In TypeScript:

Bad

function getUserData() {
  const userData = queryUserFromApi();
  // ... lots of code here
  return {
    name: userData.user_name,
    id: userData.user_id,
  };
}

This function returns an object with the keys name and id. In this example that’s pretty clear but imagine that the function was 300 lines long with 17 return statements. You’d have to read each one to make sure you understood what kind of data it could return. Since we are the ones writing the function, we already know what it can return and can provide an interface.

Good

interface User {
  name: string;
  id: number;
}

function getUserData(): User {
  const userData = queryUserFromApi();
  // ... lots of code here
  return {
    name: userData.user_name,
    id: userData.user_id,
  };
}

By providing a return interface, we guarantee that the data returned will have the properties we’ve specified, no matter how complex that function might be.

In PHP:

Bad

function getUserData() {
  $user_data = queryUserFromDatabase();
  // ... lots of code here
  return [
    'name' => $user_data->user_name,
    'id' => $user_data->user_id,
  ];
}

Similar to the TypeScript example, the only way to know what this function returns is by reading the whole function. We can do better.

Good

class User {
  public string $name;
  public int $id;
}

function getUserData(): User {
  $user_data = queryUserFromDatabase();
  // ... lots of code here

  $user = new User();
  $user->name = $user_data->user_name;
  $user->id => $user->user_id;
  return $user;
}

By using a value object, we guarantee what kind of data the function will return. Someone reading this code does not need to look inside the function to know how to use it.

Classes

Keep inheritance shallow, or avoid it altogether

The concept of building up behavior through a chain of ever-more-specific children is appealing, but in practice it creates obfuscated code that’s hard to reason about. While one level of inheritance can sometimes be appropriate, it can be hard to contain and can lead to creating “god” classes which have a bunch of unrelated methods just as a means of sharing code. The resulting children classes can have massive levels of bloat that are hidden when reading them.

If you come across a statement like $this->get_username() in a child class, where is get_username() defined? It could be somewhere else in the class, the parent, the grandparent, etc. If the class uses multiple inheritance (eg: PHP traits), then there are even more possibilities. What if the function is defined in several places? It can be very difficult to follow.

A better policy is almost always composition. “Composition over inheritance” is advice that has been around nearly as long as objects. It means that rather than an object “being” something that defines its behavior, the object “has” something that defines its behavior.

Bad

class DomainProduct extends Product {
  use Emailable;

  public function purchase(): void {
    parent::purchase();
    $email_address = $this->get_email_address();
    $this->send_email( $email_address, $this->get_product_data() );
  }
}

This looks nice and neat, but it’s a trap. The purchase() function calls four functions of its own: another function called purchase() defined on either the parent, grandparent, great-grandparent, etc., and get_email_address(), send_email(), and get_product_id() which are defined on either the parent, grandparent, etc., or the Emailable trait. To alter or debug this function, we have to search all those locations, and if those functions are defined in more than one place then we have to understand the rules of inheritance to figure out which one is called when.

Good

class DomainProduct {
  private ProductEmailer $emailer;
  private ProductPurchaser $purchaser;
  private ProductData $product_data;

  public function __construct(
    ProductEmailer $emailer,
    ProductPurchaser $purchaser,
    ProductData $product_data
  ) {
    $this->emailer = $emailer;
    $this->product_data = $product_data;
    $this->purchaser = $purchaser;
  }

  public function purchase(): void {
    $this->purchaser->purchase( $this );
    $email_address = $this->product_data->get_email_address();
    $this->emailer->send_email( $email_address, $this->product_data );
  }
}

In this version of the purchase() function, there is no question where the other functions are defined. The second purchase() is defined on ProductPurchaser. get_email_address() comes from ProductData. send_email() is defined on ProductEmailer. Debugging or modifying this class can be done much more quickly. The above example also uses dependency injection but you don’t even need that; the below examples still use composition:

class DomainProduct {
  private ProductEmailer $emailer;
  private ProductPurchaser $purchaser;
  private ProductData $product_data;

  public function __construct() {
    $this->emailer = new ProductEmailer();
    $this->product_data = new ProductData();
    $this->purchaser = new ProductPurchaser();
  }

  public function purchase(): void {
    $this->purchaser->purchase( $this );
    $email_address = $this->product_data->get_email_address();
    $this->emailer->send_email( $email_address, $this->product_data );
  }
}

Simpler still:

class DomainProduct {
  public function purchase(): void {
    (new ProductPurchaser())->purchase( $this );
    $email_address = (new ProductData())->get_email_address();
    (new ProductEmailer())->send_email( $email_address );
  }
}

Functions

Keep functions small

Functions that have a lot of lines can become difficult to read and are often trying to do too many things at once. This can make debugging or modifying that behavior very expensive as we may have to understand the context of the whole function to work within it.

Keeping functions small isn’t usually difficult when we write them, but functions only grow larger over time. We need to fix a bug so we add 5 lines. We need to add a new feature so we add 10 lines. After five or ten years, that function now has 300 lines of code and no one wants to touch it. Keeping functions small is an ongoing process which must be considered every time we are editing code.

To break up large functions, try to extract sections of code into their own sub-functions. This also improves readability because the intermediate functions can have semantic names.

That said, please don’t write obfuscated code (code that is not readable) just to make a function smaller. If you need more lines to make a function clear, do that instead!

In JavaScript:

Bad

async function sendEmail( userId ) {
  const api = getApi();
  const userDetails = await api.getUserDetails( userId );

  let emailAddress = getDefaultEmailAddress( userId );

  if ( userDetails?.email ) {
    const rawEmailAddress = userDetails.email;
    const emailValidator = getValidator();
    const validationResult = emailValidator.validate( rawEmailAddress );
    if ( validationResult.isValidAddress ) {
      emailAddress = validationResult.emailAddress;
    }
  }

  const templateId = getDefaultTemplateId( userId );
  const emailTemplate = getEmailTemplate( templateId );
  const emailContent = emailTemplate.createContentFor( emailAddress );
  const preparedEmail = prepareEmail( emailContent );

  const mailClient = getMailClient();
  mailClient.sendEmail( preparedEmail.recipient, preparedEmail.body );
}

There are lots of little operations happening inside this function. Adding comments between them would be one improvement.

The purpose of commenting is to help the reader know as much as the writer did. … When you’re writing code, you have a lot of valuable information in your head. When other people read your code, that information is lost – all they have is the code in front of them.

The Art of Readable Code

However, commenting is best used to document the thoughts that led to certain code; using comments to fix poor readability is time that could be spent improving the readability. In this case, extracting some of the operations into their own functions is even better.

Good

async function sendEmail( userId ) {
  const emailAddress = getEmailAddressForUserEmail( userId );
  const emailContent = getEmailContent( userId, emailAddress );
  const preparedEmail = prepareEmail( emailContent );
  sendPreparedEmail( preparedEmail );
}

Keep indentation shallow and avoid else

As developers, we edit code far more often than we write it from scratch. Because of this we usually add code inside places that already exist, and some of those places are indented due to if statements. This indentation then tends to increase over time as more conditions are added.

Nested conditions can be difficult to read because when the reader looks at a line, they must read upward several layers in order to know when it will execute. Using else can sometimes increase this problem because it starts a block which is already far away from its condition (although in cases with very few lines, else is fine).

Try to keep indentation to one or two levels, and replace else with a new if where appropriate. There are several techniques for reducing indentation, like inverting a condition to return early.

In PHP:

Bad

function handle_click() {
  if ( can_edit( $user ) ) {
    $email_params = get_email_params( $user );
    if ( $email_params->address ) {
       $email_content = prepare_email( $user, $email_params );
       send_email( $email_params->address, $email_content );
    } else {
      $backup_email_params = get_email_params( $backup_user );
      if ( $backup_email_params->address ) {
        $email_content = prepare_email( $backup_user, $backup_email_params );
        send_email( $backup_email_params->address, $email_content );
      }
    }
  }
}

Good

function handle_click() {
  $can_edit = can_edit( $user );

  $email_params = $can_edit ? get_email_params( $user ) : new EmailParams();
  if ( $can_edit && $email_params->address ) {
    $content = prepare_email( $user, $email_params );
    $address = $email_params->address;
  }

  $backup_email_params = new EmailParams();
  if ( $can_edit && ! $email_params->address ) {
    $backup_email_params = get_email_params( $backup_user );
  }

  if ( $backup_email_params->address ) {
    $content = prepare_email( $backup_user, $backup_email_params );
    $address = $backup_email_params->address;
  }

  send_email( $address, $content );
}

By removing the nested indentation, we make the operation of the function much more clear. However, we had to resort to ternaries, multiple conditions, temporary variables, and noop objects. This might be fine in some cases, but I think we can do better.

Better

function handle_click() {
  if ( ! can_edit( $user ) ) {
    return;
  }

  $email_params = get_email_params( $user );
  if ( $email_params->address ) {
    prepare_and_send_email_to_user( $user, $email_params );
    return;
  }

  $backup_email_params = get_email_params( $backup_user );
  if ( $backup_email_params->address ) {
    prepare_and_send_email_to_user( $backup_user, $backup_email_params );
  }
}

By using early returns and extracting some of the logic into its own function, the code becomes even more readable.

Avoid “magic” numbers and boolean arguments

Reading a function call should explain what is likely to happen. Literal numbers or boolean values as arguments to a function can make a reader confused because it’s not clear what they mean without looking at the function definition. Prefer using named variables for numbers and either descriptive strings or multiple functions for different behaviors.

In JavaScript:

Bad

sendUserEmail( user, 6, true );

What does 6 mean? What does true mean? We have no way to know. Probably someone added them later when they needed to support additional behavior in the function.

Good

sendUserEmail( { user, numberOfRetries: 6, useBlueTemplate: true } );

Creating and destructuring objects in JavaScript is so easy that a common pattern is to use a single object argument to give each real argument a name (this simulates what’s called “named arguments” in some languages).

In PHP:

Bad

send_user_email( $user, 6, true );

This code has the same problem. What do 6 or true mean in this context?

Good

$number_of_retries = 6;
send_user_email( $user, $number_of_retries, BLUE_TEMPLATE );

We can use temporary variables or constants as a form of documentation for arguments, but it cannot be enforced by the person who wrote the function, and the names don’t have to be consistent (at least if you are not using PHP 8 which has named arguments). We can do better.

Better

$email = new UserEmail( $user );
$email->number_of_retries = 6;
send_user_email_with_blue_template( $email );

Unless there are a large number of options we need to support, creating a new function for each flag is not too difficult and using a value object to group related data allows us to turn three arguments into one. This has the added advantage that any future flags can be easily added to the object’s interface without increasing the number of arguments.

Use searchable names for functions and variables

Despite many advances in editors, IDEs, indexing databases, and Language Servers, the primary method most developers use to find code in a project is plain text search. While it can be a pain to type long variable and function names, it makes them easily searchable (and all editors have autocomplete anyway).

In PHP:

Bad

$user->send();

Just try to search several thousand PHP files for the word send. It won’t be easy to find what you’re looking for.

Good

$user->send_welcome_email();

It’s much more likely that send_welcome_email is a unique string.

If something can fail, however unlikely, handle the failure

Many statements in code have a failure case. Some functions can return a falsy value if they cannot find their data. Many untyped objects might be missing a property. Code lasts a long time. While a particular failure case might seem impossible, chances are good that over many years of use, it will be hit. Always handle every possible failure, even if handling it is just throwing an error with a descriptive message or silently skipping some functionality.

In TypeScript:

Bad

function getDataFromApi(): {name?: string} { /* ... */ }
function sayName(name: string): void { /* ... */ }

const dbData = getDataFromApi();
sayName(dbData.name!);

In TypeScript, typically you are already forced to handle every failure (at least ones that produce a value into running code) because otherwise the compiler will give you an error. In the above example, dbData.name may not be set, but sayName() requires a string. This will not compile.

However, there’s a trick called a “non-null assertion” (the exclamation point above) that tells the compiler, “I know better than you; this will always exist.” That’s great if you’re right, but what if you’re wrong? A hard-to read fatal error might occur.

Good

function getDataFromApi(): {name?: string} { /* ... */ }
function sayName(name: string): void { /* ... */ }

const dbData = getDataFromApi();
sayName(dbData.name ?? 'Unknown');

There’s different ways to handle data that we expect and which isn’t there. One way would be to say throw new Error('Tried to say a name but no name could be found'). That error message would be easy to locate in the code for debugging. However, maybe we don’t care and just want to print something. In that case, we can provide a fallback without much effort.

In PHP:

Bad

function get_data_from_db(): array { /* ... */ }
function say_name( string $name ): void { /* ... */ }

$db_data = get_data_from_db();
say_name( $db_data['name'] );

It’s very common when working with a MySQL database in PHP to query a table and assign the results to an array, assuming that the array values are all what we expect them to be. It’s not uncommon for that assumption to be false, causing fatal errors with hard-to-read messages. Sometimes PHP doesn’t even throw an error and silently logs a warning instead, which means the developers will probably never know about the problem at all.

Good

function get_data_from_db(): array { /* ... */ }
function say_name( string $name ): void { /* ... */ }

$db_data = get_data_from_db();
say_name( $db_data['name'] ?? 'Unknown' );

As mentioned above, we have many options for dealing with missing data. We could throw new \Exception('Tried to say a name but no name could be found');, or we could silently skip the function call, or – as in this example – we could provide a fallback.

So, how do I write good code?

  1. Code is read more often than it is written. Write code for humans to read.
  2. Code is changed more often than it is added. Always look for ways to make existing code easier to read.
  3. Writing code usually means focusing on the goal, not the process. Take time to do the right thing with names, error handling, comments, function sharing, and function arguments.
Categories
Uncategorized

Writing a diff/PR description to get better reviews

During a recent work meetup, we had an impromptu exercise where we went over what makes a GitHub PR description most useful to the people reviewing it, particularly when not everyone is working on the same part of the codebase. Better descriptions make for faster reviews! This is my attempt to turn that discussion into a post.

More than just improving review speed, though, these tips should also make it easier for future developers (maybe you!) trying to understand what changed and more importantly, why. The “why” of a decision is so easily lost as links die and people forget.

The title

The title of a change should quickly convey what is affected. We have tons of code at Automattic and it’s easy to end up looking at a change with your mind somewhere completely different. The title can help to ground a reviewer so they understand what they’re looking at.

  1. Prefix the title with what section of code or system is being affected. Examples are Shopping Cart:, Manual Payments:, Checkout:, etc. Sometimes this is not possible or not necessary if the rest of the title conveys the information clearly, but it’s a helpful shortcut when you want to describe something inside that system. (Some folks like to use additional markers like feat from Semantic Commit Messages and while these are nice but I personally have not found them as useful outside of commits. Convince me otherwise!)
  2. Write the title in the imperative, start with a verb, and do not end with a period. This is the same advice commonly given for commit messages. The imperative tone reads more clearly when looking at a list of changes. Examples are, Add new field to checkout form, Reduce number of re-renders in contact details, Fix race condition while saving cart. Try not to use indicative or past-tense like Adds a new file or Fixed bug. No trailing period is needed because the title is always displayed separately from other content like the title of a book chapter or newspaper article.

Title example

Manual Payments: Replace 'change ownership' dropdown with input

The body

The body of the PR will vary considerably based on the nature of the changes but generally should answer three questions:

  1. How did the system being touched work before this change? We only need to describe the necessary parts, but do give some basic context (like this) for someone who’s never seen that system before! (You and your team may know the system inside and out right now but after a few years pass, you’ll have no idea what any of this means.)
  2. What is the problem with the system just described that this change is intended to resolve or improve? This is often easy but can sometimes be complex to explain well (like this). Do not rely only on a link to an issue or a Slack thread.
  3. What does this change do and how does it resolve the problem just described? A set of “before” and “after” screenshots can often be worth a thousand words in some circumstances (like this).

These three things don’t always have to be in that order, and they don’t need to be very long, as long as the context and reason are clearly conveyed (like this). It’s far too easy to just write the third part and bypass the other two.

For additional context, provide links to related P2 posts, issues, or even Slack threads, but don’t rely on those links alone. They should be like footnotes in a book; useful if someone wants to learn more but not necessary to understand the meaning. Maybe even directly quote those sources if something on the other side of a link is particularly useful. You never know when a link will stop working or who might not have access to it.

And don’t worry about writing too much! As we say at Automattic,

It’s perfectly OK to spend more time crafting your commit message than writing the code for your commit.

It’s perfectly OK for your commit message to be longer than your commit.

Another way to explain all of this is,

In most cases, you can leave out details about how a change has been made. Code is generally self-explanatory in this regard (and if the code is so complex that it needs to be explained in prose, that’s what source comments are for). Just focus on making clear the reasons why you made the change in the first place—the way things worked before the change (and what was wrong with that), the way they work now, and why you decided to solve it the way you did.

https://cbea.ms/git-commit/#why-not-how

Body example

The "Change ownership" modal in the Manual Payments form includes a dropdown menu of all users who have created a manual payment. However, to do that, it calls `get_list_of_payment_creators()` which loops through all such users and calls `get_userdata()` on each. This process can cause PHP to run out of memory before it completes, throwing a fatal error.

In this diff, we change the modal to use an input field instead of a dropdown. This is less convenient but will keep the page from crashing while we determine a better solution. For convenience, the input field is pre-filled with the current user's ID like the dropdown menu before it.

[Fixes the issue reported here]

[Before screenshot]

[After screenshot]

The testing instructions

There are many different kinds of changes, and they require different sorts of testing and review. Sometimes a change cannot really be tested manually and we must rely on automated tests. Sometimes we cannot even do that and must rely on pure code review. However, if you want to get a useful review from people who aren’t intimately familiar with the code, it’s a good idea to give them as much help as possible.

  1. Make as few assumptions as possible about what your reader knows how to do and list what assumptions you can (eg: having a Jetpack site, having a new user account, having an email product subscription). This can be surprisingly hard! You probably know the code so well that writing step-by-step instructions seems like a waste of time (it’s not).
  2. Provide links, command-line instructions, or inputs that can be copied and pasted for the reader. If these include data that needs to be customized (eg: blog ID, user ID, etc.), make sure that this is clear and, if you can, provide functional examples. For instructions that involve UI, provide screenshots. For testing that requires modifying the code itself (eg: to force a rare condition), provide a context diff (like this).
  3. Keep each instruction as simple as possible and split complex instructions into multiple steps. Use numbered steps if there are a lot so the reviewer can more easily keep their place. Split up multiple test cases into their own sections with their own bullets or step numbers (like this).

One way to make all of this relatively easy is to write down/screenshot/copy-paste each thing you did when you were testing the change yourself, but remember to proofread it in the shoes of someone who doesn’t know what you’re talking about.

I often find that while doing this I discover test cases I hadn’t considered or ways I could make the change more clear, so in the end there’s benefits to more than just the reviewer.

Testing instructions example

1. Create a manual payment by following the steps [here].
2. Verify that the created manual payment page displays without errors (previously this would cause a fatal).
3. Click to change the ownership of the payment method. Enter the userid of a different owner and save the form. Verify that the change works correctly.

The challenge

Most developers, myself included, spend a lot of energy preparing and writing code. When it comes time to post that code for review, we often don’t have a lot of energy left. Not to mention that the act of writing prose is quite different from coding and may require a significant context switch to do well.

While not always possible (or needed), let’s try to have compassion for our readers and treat the writing of change descriptions as seriously as we do the code itself. This may mean taking a break before submitting it for review. It may help to pretend that you are explaining your change to a non-developer friend and giving them instructions for testing it. If they can do it, then your actual developer friends should do great!

Do you find something not mentioned here useful in diff or PR descriptions? Please let me know in the comments!

Categories
Uncategorized

Valley of the Machines

I wrote this one-shot D&D adventure to share with my co-workers on a recent work meetup; it was the first in several years! I now share it with you all. It takes about 3 hours from start to finish and requires very little experience on the part of the players.

For the DM, the adventure is only two pages long and includes lots of text you can read directly to the table. However, unlike previous one-shots I’ve made, in this one I’ve omitted specific skill names and DCs from the scenes because I’ve found that this is often too limiting. Instead, I merely explain the problems that the party is likely to encounter and leave the specific solutions to the group. Make up the DCs on the fly as seems appropriate! This will probably require a little more familiarity with the rules than some of my other one-shots.

Valley of the Machines loosely inspired by Horizon: Zero Dawn, but shifted to a more traditional D&D setting. Enjoy!

Categories
Uncategorized

Common TypeScript errors and how to fix them

At work we recently changed the builds for our project to fail when new TypeScript errors are added, so I took some time to clean up a bunch of old TS errors in our codebase, some of which have been there for over 5 years. There were close to 600 errors when I started (down to about 30 now), and I’ve found that most of them fall into a few general categories. I’m not a TS expert, but I thought I’d write this post to explain the most common issues I’ve seen and suggest ways to solve them in case you come across similar in your own code.

Finding and reading TypeScript errors

First, let me explain how to find and read errors, because this was not obvious to me when I started working with TypeScript. Some folks may already see errors show up in their editor if you’re running the TypeScript language server, but if you want to list errors explicitly, you can run the CLI command tsc -p your-project/tsconfig.json --noEmit (you may need to prefix this command with either yarn or npm run depending on your local environment).

It’s important to note that unlike linters, the TypeScript compiler has to run on a whole project; it cannot be used to look for errors in just one file.

Reading TypeScript errors can be daunting. They are often filled with so much information that it can be very difficult to even understand what TS is complaining about, let alone how to fix it. The first thing to keep in mind is that often TS will put the actual problem somewhere near the end of the error message, so look there first.

Argument of type '{ name: string; title: string; flavor: "green"; }' is not assignable to parameter of type '{ name: string; title: string; dogs: number; flavor: "grape" | "green"; beverage: "coffee" | "tea"; }'.
  Type '{ name: string; title: string; flavor: "green"; }' is missing the following properties from type '{ name: string; title: string; dogs: number; flavor: "grape" | "green"; beverage: "coffee" | "tea"; }': dogs, beverage

The above error is saying, “you’re passing an object to a function, but the object is missing two properties: dogs and beverage“. Notice that the useful information is right at the end. I usually read TS errors from the bottom up.

Many errors come down to TypeScript saying,

  • “I don’t know what type of data this is”
  • “this object is supposed to have a property, but it’s not here”
  • “you are passing an object with a property to a function which doesn’t know about that property”
  • “this variable or property is supposed to be of type X, but you’ve provided a value of a different type”. (This last one can often be the most confusing, because types themselves can sometimes be really complex.)

Let’s look at some specific examples.

Handling falsy variables

Probably the most common error I see is something like this:

const username = getUsername();
doSomethingWithUsername( username ); // Error: Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'.

This happens when the data comes from a place (in this case, getUsername()) that has a chance to fail (it might return a falsy value like undefined or null) and you’re passing that data to a function (here, doSomethingWithUsername()) which requires a non-falsy value (here, a string).

In this case, we can solve the error by handling the failure case. Sometimes that means reporting the error to the user, but often we know that such failures are temporary (eg: during the first render of a React component before data has loaded) so we could simply provide a fallback:

const username = getUsername();
doSomethingWithUsername( username ?? '' );

Another common fix for that issue is to alter the function you’re calling so that it accepts falsy values and handles them itself; in this case changing the parameter type from string to string | undefined | null and then adding a guard:

function doSomethingWithUsername( username: string | undefined | null ): void {
  if ( ! username ) return;
  // ...
}

Also, depending on your context, you can add early returns or other guards to make sure that your data exists before using it:

const username = getUsername();
if ( username ) {
  doSomethingWithUsername( username );
}
// or...
const user = username ? getUser( username ) : undefined;

Remember that falsy values are not all alike. You might know that undefined, null, false, '', and 0 are all false in some context, but TS requires you to be precise. You may see an error like this:

doSomethingWithUsername( username ); // Argument of type 'string | undefined' is not assignable to parameter of type 'string | null'. Type 'undefined' is not assignable to type 'string | null'.

That error is saying “your variable is either a string or undefined, but you are trying to pass it to a function that accepts a string or null, and null and undefined are different”.

Solving this error is usually as easy as either changing the function definition to accept more falsy types, or providing a fallback in the accepted type:

doSomethingWithUsername( username ?? null );

Related to the above, we often gloss over implicit type conversions in JavaScript that TS will not allow.

function doThing(x: string|number|undefined) {
	return x > 12; // Object is possibly 'undefined'.
}

While JS is fine with the expression undefined > 12, TS prefers you to be precise:

function doThing(x: string|number|undefined) {
	return x && x > 12;
}

And keep in mind that binary shortcuts like && and || don’t always do what we assume. We can usually consider them to return a boolean but they may not. Notice what happens if we put a return type on this function.

function doThing(x: string | number | undefined): boolean {
	 return x && x > 12; // Type 'string | number | boolean | undefined' is not assignable to type 'boolean'. Type 'undefined' is not assignable to type 'boolean'.
}

To fix this you may have to explicitly cast your implied boolean value to an actual boolean.

function doThing(x: string | number | undefined): boolean {
	 return Boolean( x && x > 12 );
}

Another example of using non-booleans as booleans is the Array.prototype.filter function. A common way to remove falsy values from an array is to filter it through the Boolean function (or an identity function), but for some reason TypeScript isn’t smart enough to know that this removes the falsy values.

const data = ['one', null, 'two'];

data
.filter( Boolean )
.forEach( ( x: string ) => console.log( x ) ) // Argument of type '(x: string) => void' is not assignable to parameter of type '(value: string | null, index: number, array: (string | null)[]) => void'. Types of parameters 'x' and 'value' are incompatible. Type 'string | null' is not assignable to type 'string'. Type 'null' is not assignable to type 'string'.

Instead we need a type guard (see more on them below) to explicitly tell TypeScript that the falsy values are being removed. We have one in our project called isValueTruthy.

import { isValueTruthy } from '@automattic/wpcom-checkout';

const data = ['one', null, 'two'];

data
.filter( isValueTruthy )
.forEach( ( x: string ) => console.log( x ) )

Something is not a string

Another common error I saw is when a React component expects a prop to be a string, and someone provides a React element instead.

function NameBlock({name}: {name: string}) {
    return <div>Name: {name}</div>;
}

function MyParent() {
    return <NameBlock name={ <span>Person</span> }/>; // Error: Type 'Element' is not assignable to type 'string'.
}

In this case, it’s actually ok to for the variable to be a React component, even though the original author may not have considered that. If the variable is going be rendered as a React child, we can change the function definition to accept ReactNode instead, which includes both strings and components.

import type { ReactNode } from 'react';

function NameBlock({name}: {name: ReactNode}) {
    return <div>Name: {name}</div>;
}

function MyParent() {
    return <NameBlock name={ <span>Person</span> }/>;
}

A variation of this issue which comes up sometimes is when you have a number that you want to pass to something that is looking for a string, or vice versa.

function doSomething( phone: number ) { /* ... */ }
const userInput = '1234567890';
doSomething( userInput ); // Argument of type 'string' is not assignable to parameter of type 'number'.

In that case you can usually change the type explicitly by using functions like String() and parseInt().

function doSomething( phone: number ) { /* ... */ }
const userInput = '1234567890';
doSomething( parseInt( userInput, 10 ) );

Incorrect inference from JavaScript

TypeScript tries its best to guess the types of code that’s in an imported JavaScript file. Sometimes it gets this wrong. A pretty common instance of this is when a function has optional parameters.

// In a JS file:
function doSomethingOptional( name ) {
	if (name) { console.log( name ) }
}

// In a TS file:
doSomethingOptional(); // Expected 1 arguments, but got 0.

In this case, we need to convince TS that the parameter is optional. One quick way to do this is to set a default value.

// In a JS file:
function doSomethingOptional( name = '' ) {
	if (name) { console.log( name ) }
}

// In a TS file:
doSomethingOptional();

Another way to do this is to use JSDoc to define the types, a technique that is nearly as powerful as using TypeScript directly. To mark a variable as optional in JSDoc, put square brackets around its name.

// In a JS file:
/**
 * @param {string|undefined} [name] the name
 */
function doSomethingOptional( name ) {
	if (name) { console.log(name) }
}

// In a TS file:
doSomethingOptional();

A second common problem is when JSDoc has already been used but isn’t very specific.

// In a JS file:
/**
 * @return {object} something probably
 */
export function doSomething() { /* ... */ }

// In a TS file:
const data = doSomething();
data.name = 'person'; // Property 'name' does not exist on type 'object'.

The solution here is to improve the JSDoc. Again, you can use most TS syntax (although if you run into a problem you can also use Google Closure syntax, like using Object.<string, string> instead of Record<string, string>).

// In a JS file:
/**
 * @return {{name: string}} something probably
 */
export function doSomething() { /* ... */ }

// In a TS file:
const data = doSomething();
data.name = 'person';

You can also import and use types from other files by using import() within JSDoc.

// In a JS file:
/**
 * @return {import('./my-types-file').DataWithName} something probably
 */
export function doSomething() { /* ... */ }

// In a TS file:
const data = doSomething();
data.name = 'person';

Finally, it’s worth mentioning that TypeScript can also infer types (in most cases) from React PropTypes, if they’re set on a non-TS React component. So if you see an error about some prop type being wrong on a JS component used in a TS file, check the PropTypes of the component!

Using Array.prototype.includes on constant arrays

There’s a bunch of code in our project that defines arrays of strings as constants. That’s very helpful for knowing what values are allowed, but it makes it difficult to use includes() on the array.

const NUMBERS = <const>[
	'one',
	'two',
];

function isValid(num: string): boolean {
	return NUMBERS.includes( num ); // Argument of type 'string' is not assignable to parameter of type '"one" | "two"'.
}

Basically TS is saying, “we don’t know if your random string is in the array”, which is of course the whole point of using includes(). This is hotly discussed in TS circles, apparently, but for our purposes we have to convince TS that it’s ok to do, which means either claiming that the variable is part of the array or that the array is made of strings. Here’s the latter solution:

const NUMBERS = <const>[
	'one',
	'two',
];

function isValid(num: string): boolean {
	return ( NUMBERS as ReadonlyArray< string > ).includes( num );
}

Using objects as dictionaries

In JS we use objects for all sorts of purposes, and one popular technique is to use an object as a dictionary of key/value pairs where we don’t know the keys ahead of time.

const data = {};

function addValue( key: string, value: string ) {
	data[ key ] = value; // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'.

}

You have to explicitly tell TypeScript that the object is going to be used as a dictionary, which it calls a Record. You also have to specify the types of the object’s keys and values using “generics” (see more about these below); the first generic in Record is the key type (string) and the second is the value type (also string), so the type becomes Record<string, string>.

const data: Record< string, string > = {};

function addValue( key: string, value: string ) {
	data[ key ] = value;
}

Key types are nearly always strings, but if you don’t know what the value type is going to be, you can usually use the special type unknown. The downside is that you may have to further clarify the type later on, but it’s safe to do so (and much better than using the any type which you should probably avoid because it defeats the purpose of using TypeScript).

const data: Record< string, unknown > = {};

function addValue( key: string, value: string ) {
	data[ key ] = value;
}

I don’t know the type

There’s a big category of JS code that operates on any sort of data, which is often hard to express in TypeScript. There’s a lot to say about this, but for now we’ll just examine a very simple case, the identity function. As pure JavaScript, it will cause a type error.

function identity( value ) { // Error: Parameter 'value' implicitly has an 'any' type.
	return value;
}

But how can we add types for this, when we don’t know what the parameter is going to be? As mentioned above, you may be tempted to use the type any, but this is a trap as it opts out of TypeScript altogether. There’s a better way: this is precisely what “generics” are for. You can use them as variables within TypeScript code, separate from JavaScript variables. Instead of numbers and strings, they hold types.

We know that the return type of the identity function is the same type as its argument, so we can create a variable for that type – a generic – and then use that same variable as the return type. Generics are often single capitalized letters, but they can be words just like JS variables.

function identity< T >( value: T ): T {
	return value;
}

Here we’re creating a new generic called T. We then say that the type of the function’s argument is of that (variable) type. This allows us to say that the function returns a value of that same type. We’ve fully typed the function without even knowing what the data is! In most cases, TS can infer the types of generics from their call sites.

identity( 'hello' ); // TypeScript automatically infers that T is a string here.
identity< number >( 25 ); // Or you can explicitly set the value of the generic.

identity< number >( 'hello' ); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.

The signature of the Record type described above (simplified) is Record< Key, Value >. There are other “Utility types” which can do all sorts of things; check them out!

Another common example of using a generic is the React useState hook. The hook can store and return data of any kind, so how does it know the type of data it holds? It defines a generic.

const [ state, setState ] = useState( 'value' ); // TypeScript infers that state is a string.
setState( 'other value' );

const [ state, setState ] = useState< number >(); // You can explicitly set the value of the generic.
setState( 'other value' ); // Error: Argument of type '"other value"' is not assignable to parameter of type 'SetStateAction<number | undefined>'.

Of course, sometimes you really have no idea what’s in a variable and you don’t want to make any assertions about it, such as the data returned from an HTTP API call. For those cases, you can use unknown, which forces any code that tries to use that variable to first verify its type.

function getData(): unknown { /* ... */ }

const data = getData();
console.log( data.name ); // Error: Object is of type 'unknown'.

There’s a bunch of ways to assign proper types to unknown variables, usually called “type guards” (worth reading about for other reasons!). Unfortunately asserting unknown is not as easy as it could be.

Here’s one way to resolve the error by using the as keyword, but be very careful with this technique because you can easily use it to lie to TS and cause bugs. This keyword tells TS to change the type of a variable. Notice that I told TS the data might be undefined and I have an if statement to guard against the assumption I’ve made.

function getData(): unknown { /* ... */ }

const rawData = getData();
const data = rawData as { name: string } | undefined;
if ( data && data.name ) {
	console.log( data.name );
}

Here’s a similar version which uses typeof and an inline as. Always consider the JS code that will be produced when compiled. In this example, if typeof data is “object”, and it is not falsy (null is also an object), we can safely evaluate data.name even if we are wrong about the type we put on the right hand side of as. Use this powerful keyword with care.

function getData(): unknown { /* ... */ }

const data = getData();
if ( data && typeof data === 'object' ) {
	console.log( ( data as { name: string } ).name );
}

Optional component props

Sometimes an author will create a React component with typed properties, but forget to specify which ones might be optional. This happens a lot when converting from PropTypes to TypeScript because PropTypes are optional by default. It’s also very common on boolean types because we assume that false will be the default (undefined will be the default, which is falsy but not the same).

function MyComponent(
  { name, isImportant }: { name: string, isImportant: boolean }
) { /* ... */ }

function MyParent() {
    return <MyComponent name="human" />; // Property 'isImportant' is missing in type '{ name: string; }' but required in type '{ name: string; isImportant: boolean; }'.
}

The solution here is to change the prop type to be optional by appending a question mark.

function MyComponent(
  { name, isImportant }: { name: string, isImportant?: boolean }
) { /* ... */ }

function MyParent() {
    return <MyComponent name="human" />;
}

Types are powerful but unforgiving

In many TypeScript projects it’s still possible to create non-TypeScript files, so if you’re feeling uncomfortable making a TS file, just create a JS file instead. However, even if you do, you may want to keep the types of your data in mind and perhaps add JSDoc comments to help your future self.

In general, we always write code with types in mind, even if they’re unconscious assumptions; TypeScript is merely a means of making our assumptions explicit. Often that can be annoying because it forces us to drag them out of our thoughts, deal with contradictions, and figure out what syntax to use to coerce those assumptions into code, when all we really want is to add a feature or fix a bug.

The trade-off, however, is that many mistakes we might otherwise make are rendered impossible. Perhaps more importantly, no other developer (nor our future selves) will ever be able to accidentally break our assumptions. Using TypeScript is a document of how our code is meant to be used and a guard around the logic we write to be sure that it is used as intended. TypeScript requires you to be precise, but it pays you back with stability. I hope it serves you well.

Photo by Randy Fath on Unsplash

Categories
Uncategorized

The Hunted Queen

As in the before times, this year I wrote a D&D one-shot adventure to share with my co-workers at Automattic. Sadly, I was only able to run the adventure for a few people because 2020. Still, it was a fun adventure and I wanted to share it with the internet, so here you go. (It’s based loosely on a certain scene from the Witcher.)

Queen Kalia of Bragod was married for her beauty, but has become an unexpected champion of the common people. This has made her many enemies in the court, and some whisper that the king is among them. As she returns from a distant voyage, she learns that her life may be in danger. Can you protect the queen from certain doom?

Categories
Uncategorized

grepdef: a quick way to search for definitions in code

I live inside my code editor all day and something that I need to do pretty often is to search for the definition of a symbol (a variable, constant, or function). For example, I might be debugging a problem in a JavaScript file when I come across this code:

//...
let newData = translateDataFromRaw(rawData);
//...

I need to see translateDataFromRaw to understand how it works. How can I do this?

If I’m using an IDE or a project covered by a tags file, I can use the magical “jump to definition” feature. This is usually the best option as it is fast and nearly always accurate. However, if I’m using a simple code editor that does not have a “jump to definition” feature, or if the feature is still indexing my project (this can take quite a while sometimes), or if I want to find that function on the command-line for some other purpose without having to open up an IDE, then this will not work. What are my other options?

I could use grep to search my files for the string translateDataFromRaw and then find the definition from among the results. This works well and is pretty simple, unless that function is used in a lot of places. If there are hundreds of results, it may take me forever to find the one I want.

To help with this situation, I wrote a little command-line tool called grepdef. With it, I could just run grepdef translateDataFromRaw and I’ll instantly get the grep result for the definition.

$ grepdef translateDataFromRaw
./src/translators.js:function translateDataFromRaw(data) {

It’s way faster than “jump to definition” on some of the projects I work with. I even have it integrated into my editors with the plugins vim-grepdef and vscode-grepdef.

How does it work?

It’s basically the same as the grep technique I described above, except that it has two advantages:

First, it uses a regular expression to find symbol definitions unique to a specific code language. This can definitely be inaccurate, but in my experience it’s close enough.

Second, it uses ripgrep which is blazingly fast.

All together, grepdef saves me hours of development time. Maybe it will help you too?

Right now it only supports two language types: PHP and JavaScript/TypeScript, but it’s built to be easily extensible to other languages. Please suggest them as issues!

(Photo by Clay Banks on Unsplash)

Categories
Uncategorized

Unusual Things

Every year I write a D&D one-shot game for my friends and co-workers at the Automattic Grand Meetup. This year’s adventure was titled “Unusual Things” and has a certain… upside down feeling to it. I hope you like it! Here’s the blurb:

Nothing much happens in the mountaintop town of Hawkurns, where the populace mines magical crystals and their kids to get into all kinds of mischief. Recently, however, people have been disappearing, and no one knows why. Rumors of unusual things are everywhere. Can you help solve the mystery before something worse assails this small town?

If you’d like to explore the adventure yourself, click here and download the PDF! It runs in just about three hours and is designed for 4-7 characters of level 2.

Categories
Uncategorized

What I learned writing a game engine

One of the things I did on my recent sabbatical was to start coding a video game in JavaScript. I learned a lot in the process, and I thought I’d write down some of my experiences. (For the record, while I wrote the game engine myself, I used the excellent Pixi library for graphics.)

Classes are useful after all

The more I’ve written code in JS the more I’ve eschewed classes. JavaScript’s primitive object type is pretty succinct and powerful already. Also, the prototype object model has an awkward syntax when using new. The class keyword makes this better, but can increase confusion due to call site binding (class fields with arrow functions fix this, but that feature is still in stage 3). It doesn’t help that the new keyword is misleading to those familiar with classical inheritance. Lastly, using class makes it easier to use inheritance instead of composition.

I don’t actually think of any of these as problems with the language, since I find it easy to write clear code with just functions that pass around data. In my experience, web applications do best when they never mention the new keyword at all. However, my first attempt to apply this methodology to game development ended in quite a mess.

It turns out that while most of the web application data I’ve dealt with is easily represented and moved around using primitive types, the entities in a video game have so much internal state and so much interactive functionality that a one-way flow approach (like the one popularized by React) is terribly inefficient. The way I did it, rendering each frame was a nightmare of indirection.

Keeping the state and functions together within each object and sharing those functions using inheritance ended up being a whole lot simpler. It also meant that I had real object types that could be validated using Typescript.

Math is useful after all

When I tell people I’m a programmer, they often think it means I’m great at math. I’m not great at math; that’s why I like computers so much! But it turns out that dealing with virtual objects in a virtual space requires quite a bit of algebra, trigonometry, and calculus.

It doesn’t help that most of the game development tutorials I came across assume that you’re using an existing game engine to do the work. The first time I tried to figure out how to calculate the angle between two sprites, all I could find was instructions for slerping quaternions.

Do you know what a quaternion is, or spherical linear interpolation? Well, they’re totally unnecessary in this case. In 2d space, all I really needed was arctan2(), which I probably learned about in high school and subsequently forgot. But it took me a while to figure that out. I think that if I had been taught math in school in the context of game development, I probably would have paid a lot more attention.

Making a scripting language is great

As I was developing events in the game, I wanted a way to easily centralize dialog such that I didn’t need to write code to create conversation trees (actually they’re graphs!). I did this by creating a JSON file that encoded dialog nodes, each with their own responses, and links between those responses and other nodes.

This worked really well, but I quickly realized that I needed to also encode functions into these nodes so that actions could be taken when certain choices were made. When a player insulted the captain of an enemy ship, for example, I wanted to decrease the happiness of that character and possibly have the enemy ship attack. Furthermore, I found that when a ship did attack, I wanted to encode the ship’s behavior itself. In short, I began to write the content of my game in a script.

At that time I was putting JavaScript functions into my JSON file to encode actions. That meant that it wasn’t really JSON anymore. If I wanted to serialize my nodes, I’d have to put those functions into strings. This immediately felt wrong because I’d have to let my game engine execute arbitrary JavaScript, which is about as big of an anti-pattern as you can get.

Professional game engines are not written in JavaScript, so they can use JS as a scripting language, but I would have to do something else if I wanted to allow executing arbitrary scripts. I began to wonder if I could write my own, very simple scripting language for the purpose. I was fortunate to discover Nearley which made that both possible and fast.

I first wrote a grammar that looked something like JavaScript, except without any variables or named function definitions (although there were anonymous functions). Statements were composed of expressions which were composed of function calls and primitives. The functions were all pre-defined; this was my game’s API. All of that worked pretty well, but I ran into a problem when I added if statements for control flow.

Each statement had to end with a semicolon. Since the language’s programs had to be encoded as a JSON string, and JSON strings cannot have newlines, it was imperative that I could chain statements together on one line. However, if statements in most languages do not end with a semicolon. I had a really hard time encoding this into the grammar, and so each if statement had to end in a semicolon as well. With that requirement I found conditions really awkward to write.

if (distanceToPlayer() < 5) { if (getNpcHappiness('enemy1') > 2) { gotoNode('enemy1Greeting'); }; if (getNpcHappiness('enemy1') <= 2 { attackPlayer(); }; };

Eventually I realized: I had multi-argument functions and I had anonymous function definitions… so I could encode control flow as a function call! That is, instead of this procedural condition statement:

if (distanceToPlayer() > 5) { accelerate(); };

I could write this functional version:

if(distanceToPlayer() > 5, { accelerate(); });

The difference was subtle but powerful. I was able to remove nearly half of the grammar I had written, since the function call syntax was already supported.

My scripting language is not extremely powerful, but even with a simple grammar in place I was able to write most of the events of the game entirely in one JSON file. I can see how it would be possible to write a dedicated dialog and level editor for that file, which would totally separate the game and its engine.

Conclusion

I’d love to show you my game, but I was pulled into other adventures on my sabbatical before I was able to finish writing it, and it has a long way to go. Maybe I’ll pick it up some time in the future, but for now I’m happy about all the things I learned getting it as far as I did. If you want to look at my code, feel free to browse the repo!