Skip to content

The CODE Tab

Jacob Kelter edited this page Jan 25, 2023 · 2 revisions

NetLogo code has multiple purposes. The code must be "efficient" and functional, but it should also be readable to someone who wants to know what the exact rules of the model are.

The Models Library lives by a slightly stricter Code (pun intended) in that each model should also be a good code example since users use our models both to learn how to program and as templates for their own new models. Ideally, all of the code in every model in the Models Library should be technically, stylistically, and visually exemplary, because looking at our models is one of the main ways that NetLogo users learn how to code and learn what good code looks like.

Below are a lot of specific dos and don'ts for NetLogo code, but it's not an exhaustive list.

Variable Naming

Name variables like this: leopard-spot-count, and not like leopardspotcount or leopardSpotCount or leopard_spot_count.

We only allow use of capital letters if it is accepted abbreviations. For instance, in the library model Climate Change, infrared rays and carbon dioxide are abbreviated respectively IRs, and CO2s. So if it is an accepted abbreviation, it's fine. If it's not, don't use capital letters.

Breed names

Breed declarations require both a plural form and a singular form. If there is not a clear singular form of the breed name, prefix it with "a-".

breed [ wolves wolf ]
breed [ sheep a-sheep ]

Naming of booleans

Every Boolean (true/false) variable and reporter (and switch) should have a name ending in a question mark, like this: grow-grass?.

Procedures

Naming

Try to name your procedures so that they "read" easily in your code. A lot of the time, your procedures are "actions performed by agents," so ideally you'd like to ask sheep [ do-the-thing ] instead of ask sheep [ foo ].

Ordering

Put your procedures in a logical order: setup, then go, then supporting procedures, etc..

It also makes sense to group turtle procedures together by which breeds use those procedures (if they are indeed "breed-ed" procedures.

Big headers intended to group procedures together (like ;;;;; SETUP PROCEDURES ;;;;;) are not required and tend not to be very useful unless you have a lot of procedures that you think need to be grouped in order for the code to be more readable

Abbreviations

For built-in primitives

These are the only OK abbreviations: fd, bk, lt, rt. And even then, you should probably use the full version (i.e., forward, back, left and right).

Don't abbreviate: ca, cro, crt, pu, pd, ppu, ppd, bf, bl, cd, cp, ct, ht, st, se.

The idea is that for readability, we should only use a few of the most common abbreviations. In their own code, users are free to save themselves typing using abbreviations if they want, but in sample code, we should favor readability.

For your own variables, procedures, etc.

Again, it's probably best to NOT abbreviate UNLESS, that abbreviation is very standard in the field which you are modeling (e.g. "carbon dioxide" -> "CO2"). Even then, you should make sure to preface and explain that abbreviation in the INFO tab.

Coding Style

Be turtle-centric!

For example,rewrite set heading heading + 10 as right 10.

But also use your judgement. For example, set heading random 360 is better than right random 360, since it is obvious that the resulting angle is not affected by the turtle's current heading.

Stopping the go button

Does the model have a natural stopping point, for example, when all the turtles are dead? If so, the "go" button should "unpress" itself if the model reaches that point, using the stop command.

If the "go" button uses stop to stop itself, the stopping check should be at the top of the go procedure, not the bottom.

This is wrong:

to go
  ask turtles [ ... ]
  if not any? turtles [ stop ]
end

Do this instead:

to go
  if not any? turtles [ stop ]
  ask turtles [ ... ]
end

That way even if the user tries to press "go" again, nothing will happen. They shouldn't be able to force the model to step forward by repeatedly pressing "go" (especially since in some models that might cause bad behavior, such as a runtime error).

BehaviorSpace also expects this "stop check" to be at the top of the go procedure to be if you want to still have the final state of the model included in the results.

Not all models have natural stopping conditions (such as all the turtles being dead) — many models just run forever and therefore have no need for added code like this.

Avoid globals!

Global variables are an endless source of errors and they can make a model much harder to understand.

In a nutshell: the main problem with global variables is that they can be modified from anywhere in your program (including plot or widget code) so it's hard to reason about the values that they can possibly hold at any particular time. Here is a more detailed explanation if you are interested: Why you should probably avoid global variables in NetLogo.

If you are thinking a global value is necessary, consider making it a NetLogo widget (e.g. slider, chooser, etc.) so that users can tinker with it.

Don't use globals as constants

If, for example, you wanted to use tau instead of pi, you could do:

globals [ tau ]

to setup
  clear-all
  set tau pi * 2
end

But this has two problems:

  • Until setup is called, the value of your uninitialized tau variable is 0, meaning that any code using tau will give wrong results.

  • You have no formal guarantee that the value always stays the same (you could, for example, have a forgotten, and less precise, set tau 6.2831853 lying around somewhere in the code).

Don't hesitate to use reporters instead of globals to simulate constants:

to-report tau
  report pi * 2
end

This ensures that the value of tau will always be the same. And don't worry about the use of a reporter making your code slower until it becomes a demonstrable problem (which will probably never happen).

Don't forget that boolean values can be used directly

It's not uncommon to see code like:

ifelse any? out-link-neighbors
  [ set has-neighbors? true ]
  [ set has-neighbors? false ]

This can be replaced by:

set has-neighbors? any? out-link-neighbors

Avoid = true or = false

Writing if condition? = true is unnecessary. It can be replaced by if condition?.

Similarly, if condition? = false can be replaced by if not condition?.

The only exception to that rule is when checking the value of a global variable that could possibly still have its "uninitialized" value of 0. But you should probably try to avoid that situation anyway.

Don't leave print, show, type, and write statements in the code

If those were used for debugging, they should not be in the final version of the model.

If you want to communicate with the user, use an output widget and output-print, output-show, output-type or output-write instead.

Don't leave development-related stuff in the code

For example, if you've used the profiler extension (always a good idea!), don't leave the profiling code in your model.

Don't use count when you could use any?

Don't use if count turtles > 0 (or >= 1) when you could use if any? turtles. And don't use if count turtles = 0 when you could use if not any? turtles.

Initialize your variables

NetLogo initializes all variables to 0. That's fine for numeric variables, but variables of other types should be initialized to a value that makes sense, e.g.: false for a boolean, "" for a string, [] for a list, no-turtles for an agentset, nobody for an agent reference, etc. Even if it doesn't make a difference when your model runs, it could make one for somebody who is trying to analyse your model from the Command Center or from BehaviorSpace. For example, in the Flocking models, an expression like mean [ count flockmates ] of turtles would fail right after setup if flockmates was not initialized to no-turtles.

When appropriate, prefer set-default-shape to set shape

The nice thing about set-default-shape is that if you create turtles from, e.g., the command center, they automatically get the right shape.

Some primitives are best avoided...

ask-concurrent

In the documentation of ask-concurrent, it says:

This primitive exists only for backwards compatibility. We don't recommend using it new models.

...and we don't want new models, particularly in the Library, to use it!

who

While there are some legitimate uses for who numbers, those are few and far between. If you're thinking of using who numbers for something, think again: there is most probably a better way. The reason being is that often times, code dependent on who has a tendency to be easily broken by edge cases.

no-display

As former NetLogo developer Seth says:

99%, if not 100%, of uses in the past were to compensate for the nonexistence of tick-based updates in older NetLogo versions, or were just because people didn't understand they should be using tick-based updates instead.

There are rare cases where hiding updates is either desired or necessary. But in general, you shouldn't be hiding things from the user.

Communicating with the user

If the model needs to tell the user that something has gone wrong, use user-message. If you use show or print, the user might not see it because they might have hidden the command center.

If the model wants to print messages from time to time as it runs, it should use output-print (and/or the other output- commands). Add an Output area to the interface for the output to show up in. Doing so has several advantages:

  • The output will be visible even if the user has hidden the command center.

  • You can give the output a fairly narrow area of the screen if you want, whereas the command center is always the full width of the window.

Avoid HTML in user-message dialogs

It may look good on your own computer, but there is a very high chance that it will not look good on someone else's computer and that the HTML codes will leak out in the message text. This is because NetLogo splits your message in multiple JLabel objects (https://github.com/NetLogo/NetLogo/blob/5.x/src/main/org/nlogo/swing/UserDialog.java) and that each JLabel must have complete, HTML code independent from the other ones. Even if you achieve this on your machine, you have no guarantee that the split will be the same on another machine. It varies from OS to OS (in rare cases, it can also vary with the user's JVM font settings).

Formatting

The code should be as easy to read as possible. Look at models in the Library to see what easy to read code looks like. There is no "uniform style" (and hence no linter) for spaces, bracket usage, line breaks, etc.. That being said, it is important to pick a style and to apply it consistently.

Try to apply the following guidelines:

Use the auto-indenter!

As of NetLogo 6.0, there's a built in auto-indenter that will enforce 2 space indenting. Just highlight your code and hit TAB and the auto-indenter will take care of the rest!

Avoid long lines

In the Models Library, we limit line length to 85 characters. Why 85? Because that was the limit for the IABM textbook and the Library should be consistent. Having shorter lines has lots of advantages anyway:

  • It encourages good practices like breaking up a long expression into a few local variables and/or reporters.
  • It makes it easier to quote a piece of code in a blog post, a paper, or something like that.
  • It works better for people that have older screens with lower resolution.
  • You can zoom in the code (using Ctrl-+) and still see complete lines (very useful when teaching, amongst other things).
  • You can keep your NetLogo window small if you want to.
  • It makes it easier to view side-by-side diffs on GitHub or in your local diff tool.
  • It just generally reads better.

Do you want a good trick to make it easier to follow that rule? Use a ruler! Paste this into your code:

;-----------------------------------------------------------------------------------;

That's 85 characters long.

Use only one command per line

Code like:

set fly? false set run? false if breed = frogs [ set jump? true ]

reads much better as:

set fly? false
set run? false
if breed = frogs [ set jump? true ]

Use one blank line between procedures

No more, no less. One is enough.

Remove trailing blank lines

Remove any extra blank lines from the end.

Don't overdo it with the parentheses

They can be useful to clarify the order of operations when it is not immediately obvious but can be omitted most of the time.

In particular, avoid if (condition?) [ ... ]; it's just noise. Parentheses are known to be points of confusion for novice programmers. If you don't need them, don't use them.

Put a zero in front of fractional numbers

Use 0.2 instead of .2. The leading dot, by itself, is just too easy to miss.

Put spaces inside square brackets

E.g.:

print [ size ] of turtles

Don't put spaces inside parentheses

E.g.:

print (size + 2) * 3

Comments

Make sure code is adequately commented. Don't assume that users are expert programmers. It's particularly important to comment anything unusual, tricky, or obscure in the code. On the other hand, it is not necessary to comment every local variable or every line of code. This is because when you're writing code, you should try to do so that it is "readable." One of the design aspects of NetLogo that makes the language so unique is that you can write code that reads fairly close to a regular sentence. Use that power!

Use just one semicolon (;) to introduce comments. Some NetLogo models adhere to a "double semicolon" intro, but we think that the source that is an old LISP convention. Consistency is key.

Label global and agent variables

Every global variable and every turtle or patch variable should have a short comment (usually just one line) saying what it is for. The comment should give some indication of whether the variable holds a number or a string or an agentset or what. (Note that saying e.g. "how many" is sufficient to convey that it's a number.) Here are some examples:

globals [
  %infected            ;; what % of the population is infectious
  %immune              ;; what % of the population is immune
  lifespan             ;; the lifespan of a turtle
  chance-reproduce     ;; the probability of a turtle generating an offspring each tick
  carrying-capacity    ;; the number of turtles that can be in the world at one time
  immunity-duration    ;; mean number of weeks immunity lasts
]

turtles-own [
  status  ;; equals one of "susceptible" (healthy but not immune), "infected" (sick and infectious), or "immune" (healthy and immune)
  age     ;; how many years old the turtle is
]

Identify turtle and patch procedures

Any turtle or patch procedure should be commented as follows:

to move ; turtle procedure
  forward 1
end

If it's an observer procedure, it's not necessary to say so explicitly:

to go
  ask turtles [ move ]
end

Other general commenting rules

Don't leave commented-out code lying around. It's not doing anyone any good.

HubNet Setup

hubnet-reset should only be called in startup not in setup. setup should not destroy any information needed to communicate with clients. If the teacher needs to kick out all the client s/he can use the RESET-INTERFACE button in the Control Center which you may want to point out in the info tab.

BehaviorSpace

Remember to verify any included BehaviorSpace experiments. If you change the names of anything, old experiments might not run anymore.

Clone this wiki locally