Good coding style in Inform 7

Does anyone have suggestions they want to share for writing clean, maintainable code, either general tips that apply to Inform 7, or Inform-specific tips?

I don’t have a programming background but I’ve been reading about this recently. I’ve come across tips like

  • define things close to the place in the code where they are used
  • a huge part of making code maintainable is making it readable/understandable
  • give things specific, unambiguous names to make it easy to figure out what they are for
  • code that’s “tricky” is likely not good code
  • make the intended route through the code clear, putting the expected route first so other people reading it can follow it (e.g. try to make the “ifs” the expected behavior and the “elses” the problems/less likely behavior)
  • make stuff extensible (i.e. make it so that it’s easy to extend things beyond their immediate intended use)
  • if you have lots and lots of “decision points” in a chunk of code (ifs, thens…), it may be a good idea to break it into parts and/or simplify. The book gave some numbers to use as a rule of thumb but I don’t know where my notes are at the moment.

Most of these come from the book “Code Complete” although I’m going from memory here so take my version with a grain of salt. And I suppose the readability factor is more crucial in things that could end up publicly maintained, such as extensions, than in individual works where the code is not public.

If there’s already a resource that covers these sorts of things as they apply to Inform, I’d be interested to hear about that too.

2 Likes

Code Complete is a fantastic book.
On readability, I think readable code is a good goal in itself. The exception is when the alternative significantly improves performance, which you very often can’t tell without measuring it manually. Otherwise, readability means usability, i.e. code you can reason about easier and repurpose quicker. Aesthetics also come into play: out of all my code, the simplest and clearest parts make me the proudest, which is great for keeping up motivation.

More musings on general coding (these being just my opinions, of course): * Good code is careful about spurious state. When a lot of free-floating variables all interact, it becomes tough to reason about invariants and how the various code states interconnect. If you can couple loosely, do that. Avoid global state. Mostly, if something does not need to be remembered, calculate it.

  • Good code follows consistent idioms. Just like OOP languages encourage the use of objects, I7 has the concept of rules and rulebooks, so those should be used when appropriate rather than reinventing the wheel.
  • Good code does not repeat itself. Repetition slows productivity and multiplies errors, which is why we have the DRY (“Don’t Repeat Yourself”) idiom.
  • Good code is terse and specific. Just a guideline, but I’ve found the old OOP adage about “doing only one thing, and doing it well” useful in general.
  • Code what you need now, not what you might someday need. With increasing skill at coding, it’s tempting to create elaborate systems that “should” exist in the game without a clear idea of their purpose. It’s like learning big words as a kid: just because you know them doesn’t mean forcing them in would improve the work.

There’s probably a ton more that I just can’t think of at the moment. Would love to see what the rest of you guys have to say on this.

1 Like

It’s true that readability is important when someone else will read your code. Quite often, that someone else will be you, six months or a year down the line, after you’ve forgotten many of the details about what you’ve written. Future you will be grateful if you’ve written readable code, even if your project isn’t public.

1 Like

Thanks for posting these.

I have a couple of other thoughts for Inform.

  1. I make heavy use of the headings. Volumes, books, parts, etc. Grouping and subgrouping like-ish things and giving them clear headings. I’m probably so thorough about this that I end up making enough headings that it starts to become counterproductive if viewed from the index (too many chapters to pick from) but:
  • It helps a ton when I’m scrolling around
  • It gives specificity to the thing. I get familiar with what bits look like or where they are, which helps me feel I Know the project
  • It helps anyone, starting with me, in the longterm
  1. After a big bout of progress and when you’re at some kind of stable point, I find it helps to make a more macroscopic pass over the source. Since all the time I’m creating little routines and variables, and it’s too fiddly to move them to the right place at the time, or I may not even have a right place yet.

So when I come to a plateau, I go through the source chronologically:

  • I move bits around and put them in the right sections
  • I try to make the sections and their names better
  • I check if I’ve orphaned any routines or variables (but don’t throw stuff away too soon - I’m always doing that. You can instead move it to a graveyard section of sorts, but call it something happier, like ‘Big Unused Volume in the Sky’)
  • I can keep an eye out for stretches of code that are basically doing the same thing. Maybe I can consolidate them and make them one routine? etc.
  • Doing this also refamiliarises me with the whole thing in general

-Wade

1 Like

As far as euphemisms go, that one deserves an award.

1 Like

I think this makes just as much sense in reverse:

  • Take shortcuts (i.e. if the player isn’t actually gonna drive in the car, just say The Cadillac is here. The Cadillac is an enterable, fixed in place container.)
1 Like

A few basic things I try to follow:

  • every statement must end in a period, and one statement per line
  • every line in a rule must end in a semicolon
  • at least one blank line before and after every rule
  • either begin/end or tabbed blocks, but not both
1 Like

Regarding if/else constructs, alternate scenarios (failures, shortcuts) belong within “if” blocks with some sort of “return” (rule fails, next statement for loop constructs, etc). The (single) main scenario belongs I indented (I.e. Not within an “else” clause) after all of the fail/shortcut cases. Avoid “else” clauses. This is known as the “return early” pattern and encourages writing procedures that have a single “main scenario”. It really helps keep complexity down.

I would disagree slightly. Or as I put it on Quora:

Of course, I seldom use else in C++, but sometimes it does make the code clearer. Or you use ternary assignment. Or you use polymorphism, std::map lookups, or even in some cases switch. It all depends on what you want to express.

In addition, I’m not sure the exit-early strategy is all that useful as an I7 idiom. If you have deeply nested if statements in a world where rulebooks and relations are a core concept, you’re probably using the wrong tool.

Thanks, everyone, for your suggestions!

Sorry, I didn’t mean to suggest that readability isn’t a worthwhile goal. I do think it’s valuable for various reasons that people have pointed out, even if no one else sees your code. My train of thought when I wrote what you’re responding to was something like “I hope people who are new to Inform 7, and writing just for themselves, and still trying to figure out how to make things function at all, don’t get overwhelmed by these ‘rules’ about the finer points of style and give up on Inform altogether.”

So what I perhaps should have said was, while it’s important to be able to understand your own code, a goal of trying to make it super comprehensible to others who’ve never laid eyes on it can be lower down on the priority list than getting the code to compile, and function, particularly when you are first starting out. Of course, if you’re trying to write code for a wide audience, that may shift the “comprehensible to others” priority within the priority list. Either way, it’s a good idea to try to make the code readable as you reasonably can.

Code Complete talks a lot about loose coupling, and I was having a hard time getting my mind around it, and how to achieve that. One difficulty I have run into is this: I’m operating under the assumption that (all other things being equal), passing in a variable to a routine is better than using a global variable, but some rules do not seem to allow you to pass in variables.

In other words, it seems as though something like this

To peruse the contents of (C -a container): (do something with C)

is generally preferable to something like this:

[code]The currently perused container is a container that varies.

This is the peruse the contents of a container rule:
(do something with the currently perused container)[/code]

But there are times when you may want to use a rule (say, you’re writing an extension and you suspect people may want to modify this block of code), and I’m not sure how to avoid global variables in that case.

It’s also possible I’m approaching this the wrong way.

Are you talking about this sort of thing

Place is a room. Place is east of Someplace Else. The description of Place is "A generic place."

as opposed to something like this?

Place is a room. Place is east of Someplace Else. The description of Place is "A generic place."

More of a design guide:
It’s better to use two specific rules than one overly general rule. the problem with general rules is that they can combine in unpredictable fashion. It’s best to have a rule for each specific purpose not try to think of the 10 seemingly simple rules you’ll ever need.

Don’t number headings. Numbers get out of order if you want to rearrange things. Use named headings (or anonymous ones).

Headings should be self-contained. The code under a heading should be complete and independent. This means you can move the heading around (e.g. to an extension or a new project) without unexpected breakage. All source code should be under some heading. Naming headings can solve the problem of “where do I put this bit of code?” / “where is that bit of code?” - if you have made some headings but you can’t pick which one, make a “misc” heading.

As Wade suggested, draft as a stream of content. Then place related things under headings.
If you need to read code (e.g. a phrase or a rule) then one sentence per line is easier to scan:

Flipping is an action applying to one thing. Understand "flip [something]" as flipping. Check an actor flipping: abide by the can't switch on unless switchable rule; if the noun is switched on, try switching off the noun instead; try switching on the noun instead;

For definitions (e.g. things, rooms), paragraphs are more compact:

Place is a room. Place is east of Someplace Else. The description of Place is "A generic place."

But if you haven’t finalised a definition it’s easier to edit in single lines:

Place is a room. Place is east of Someplace Else. The description of Place is "A generic place."

Freeze tested code into private extensions This makes more sense for large projects. It’s tempting to noodle with code in front of you, putting finished work in an extension helps you to focus on the part which needs work. You can also use this technique to break up a too large source into smaller workable pieces.

Use say phrases to abstract long texts §5.13. Making new substitutions. A long text is difficult to edit particularly in tables. This is especially effective for building complicated text variations out of simple editable pieces.

Centralise includes The wrong order can break compilation - the order of include statements should be visible so you can fix it.

Use tables for object data Tables are a compact way to construct a large number of objects. §16.16. Defining things with tables.

Yeah, that’s fair. I think the way to approach that is to tell people to write stupidly simple code as much as possible. To avoid getting cute if they can. This is, I think, one of the reasons why we rarely see the Equations facility used much: it’s a bit fancy for what you want to do with it.

Well, I think the best way of putting it is to limit interactions between with the internals of different objects. I will admit it’s perhaps not the clearest advice for I7 (I’ve been trying too hard to be curt and succinct lately), so instead I’ll put it like this: create many simple individual building blocks that can interact or work alone rather than a few large, unwieldy objects that require specific handling.

If you want rules to fetch data, rulebooks can be object based. That allows you to pass variables inside them, like so:

The peruse the contents of rules are an object based rulebook with default success.

A peruse the contents of rule for the calebasz: say "It's a calebasz!"
A peruse the contents of rule for a beaker: say "It's a beaker of sorts!"
Instead of examining a beaker (called item): follow the peruse the contents of rules for the item.

A beaker is a kind of container. A beaker is usually unopenable and open.
The calebasz, the bowl and the drinking glass are beakers. 
The Shop is a room. All beakers are in the Shop.

Is that what you mean? Otherwise, both actions and activities allow for setting internal variables.

A related thing which is relevant for I7 (and I6, and all IF coding, I expect): rely on as few variables and properties as possible.

Say you have a bucket which can be empty or full. You could write an action which does

Carry out emptying the bucket:
  now the bucket is empty;
  now the description of the bucket is "It's an empty bucket.";
  now the weight of the bucket is 1;

But this is fragile because you’re updating three things, and then you’re updating the same three things in the fill routine, and then maybe you add another routine which updates just the description when the bucket gets dented, and then your life is messy.

Instead you want to make everything rely on one property. You write the description as “It’s [if empty]an empty[else]a full[end if] bucket.” (And never change the description property at all.) So it can’t get out of sync.

If you later add a “dented” property, the description has to be updated, but that’s good – you have to go look at it and think about how your (now) two properties interact.

2 Likes

I’m talking about a situation where you are carrying out the same process with each item, and the process will be invoked from other code. I used containers as an example of a variable but I’m actually dealing with tables. So, something like this would probably be more relevant:

Example 1 (phrase that takes a variable):

To list all the planets in (T - a table name): let planet-list be a list of texts; repeat through T: if there is a planet entry: add the planet entry to planet-list; say planet-list.

versus this:

Example 2 (global variable plus rule):

[code]T is a table name that varies.

This is the list all the planets in a table rule:
let planet-list be a list of texts;
repeat through T:
if there is a planet entry:
add the planet entry to planet-list;
say planet-list.[/code]

I want this to be able to work with tables that others have created, so I won’t necessarily know what all the tables are ahead of time. And if I were to create a separate rule for each table, all the rules would look pretty much identical. So I don’t know that a rulebook would make sense for this. I will take a closer look at activities, though–it hadn’t occurred to me to try one of those. Thanks!

For consistency I prefer the first. It’s not much of an issue, but if people are getting errors with rules and phrases running into each other, that’s what I recommend. Periods for statements, semicolons for rules.

1 Like

One nice effect of putting a statement/sentence on each line is that it produces cleaner diffs.

1 Like

The other day I came across some resources related to that coding book in the external links on its wikipedia page, including some coding checklists.

I never thought of that!

Thanks for all the suggestions, everyone!

There are some things that I think are universal to good coding style across languages, in decreasing order of importance:

  • Be consistent. The same rules should apply everywhere and in all cases. Ideally, it should be easy to write a code generator that makes style-conforming code.
  • Be concise. Symbols should only be in the code if the language requires them, if they enhance clarity (eg, parentheses that aren’t strictly needed but which make lines understandable to people who haven’t memorised the precedence table), or if the rules about whether you can omit them are byzantine (eg, semicolons in javascript).
  • Be considerate. Constructs that induce error, particularly in someone who is reading code they didn’t write, shouldn’t be used or should be used with a warning (eg, commenting fall throughs in switch statements, in languages that support that behaviour).
  • Be idiomatic. Favour language-specific constructs over “generic” ones; favour commonly-used constructs over arcane ones; try to follow the lead of major texts in the language (unless you think their authors erred).
  • Be meaningful. Write code that conveys information about itself.
  • Be truthful. Write code that looks like it does what it does; favour constructs where the syntax matches the semantics over constructs that mask the semantics of the language (eg, don’t use class in JavaScript).

In I7, we can translate those principles into specifics:

  • Consistent: Use the same style throughout a project.
  • Concise: Use “it” and “here” where appropriate (and order declarations so that this can be done). Use shorthand versions of statements such as “A vase is here. The description is…”, as opposed to “A vase is in the Living Room. The vase’s description is”. Allow periods within quotes to end statements, but only at the end of a line.
  • Considerate: Avoid dropping into i6 unless you have a very compelling reason.
  • Idiomatic: Use “otherwise”, not “else”. Use rulebooks. Put guard logic in rule declarations, rather than having if/else statements inside rules, whenever possible. Store “loose” data in tables, not global variables. Use relationships to point between objects. Put all brief statements about an object on the same line.
  • Meaningful: Use either/or properties or named properties instead of number or string properties, whenever possible. Give rules meaningful names.
  • Truthful: Avoid using objects to represent things that aren’t objects in the world model; when you do, make a kind explicitly for use as such.

Obviously this is all debatable and incomplete.

Regarding not numbering sections because they get out of order: Inform can auto-number headings and update them on the fly if one is added in the middle (at least on Mac).

1 Like

If the IDE is renumbering sections for you, you learn to ignore the section numbers because they could change. So then who’s paying attention to them? Leave 'em off.

1 Like