Documenting your code

Putting comments or not in your code is the subject of heated debates among programmers. Some say you shouldn't write comments; others say that you always should.
However, the idea that you should always document your software is not controversial. Almost everyone agrees that documentation is necessary, even if some people argue that it should be written separately from comments.
In this study guide, we'll cover:
  • What is self-documenting code and how to achieve it.
  • What to write in documentation comments: selecting the information that is crucial to capture.
  • How to write comments: syntax and style.
  • How to generate pages in the code reference from your code.
In Godot, you can write comments in your code that the engine uses to generate documentation. We often call them docstrings, short for documentation strings. You write them with two hash signs (##) above a definition (a class, a function, a variable, etc.).
## This is a documentation string for the health variable.
## The variable represents the player's health.
var health := 100
Docstrings are a very efficient tool for documenting your code. Everything is in the same place, making it easy to update the documentation when you change the code.
I think documenting code with docstrings is excellent because:
  1. The documentation lives next to the code. It's easier to check that the two are in sync.
  2. Since you're already working in the script editor when writing code, you can stay in the flow and write documentation as you work. You're more likely to do it.
  3. You can quickly write the documentation right after writing a script, so the code is still fresh in your mind.
Godot generates code reference pages from these docstrings, so the documentation is not only included in your code but also made easier for teammates to read without diving into the source code.
Why do some people say you shouldn't write comments?
The main argument against writing comments is that they can become outdated and misleading. This is true of any documentation.
It is even true of code! You can write a function you end up never using, which stays there, useless. We call that dead code. Every project needs frequent cleanup, and cleaning up comments is no more or less complicated than maintaining the rest of the code or external documentation. Like any other part of your project, you need to be diligent about it.
Advocates of self-documenting code argue that you should write code so clearly that comments become unnecessary. It's a great goal, but it's not always possible.
At GDQuest, our philosophy is to do both:
  • Try to write self-documenting code, that is, code with the clearest names and purpose possible. We spend time choosing and adjusting variable and function names. We do our best to ensure that the code remains relatively easy to read and understand, even without comments.
  • And then, we still write comments to clarify the context and reason for writing the code in specific ways, document limitations, or provide usage examples.
For me, there's no contradiction between self-documenting code and comments. Both complement each other well. After all, regardless of how you name variables and functions, you can't communicate intentions this way:
  • Why the code was written in a certain way.
  • What tradeoffs you made.
  • How it's meant to be used.

Why should I document my code? I wrote it!

People say to document code for other people, but often, those "other people" are yourself. Admit it. You've written some code that was hard to read again after a while, and you stumbled upon a function for which you don't remember the reason. You're not alone! This is how coding goes.
Be kind to your future self, and treat yourself with care: one of the ways you cater to your future confusion is by documenting well.

Self-documenting code

The most crucial aspect of self-documenting code naming. Other aspects matter also, such as structure and organization, but naming takes the lion's share. It is surprisingly hard to get right, but it is the primary way we interact with code.
That doesn't mean you should be anxious about naming from the start. Code is iterative, and you may not know what a variable will be used for. For example, you might start with a variable named health but then realize you can acquire temporary health boosts, so the variable should be called base_health.
Instead of stressing about it, always be willing to examine your name choices at different stages of your code. Feel free to use long, clear names. It's better to have a long name that is clear than a short name that is ambiguous.
In some languages, you can use refactoring tools to rename a variable or function across a project. In Godot, there is unfortunately no such tool. We made a contribution to the Godot engine to add this feature, but it's not in the stable version yet. Therefore, giving your variables distinct names is important so you can easily search for them.
The other aspect of self-documenting code is writing the clearest code you can. This is largerly subjective, but attempt to use common patterns and code styles. Whenever you can, use the simplest code you can; favor clarity over performance.
Below are some recommendations for naming. I classified them from least controversial (most programmers would agree) to most controversial (some programmers would strongly disagree).

General suggestions

Here are some common guidelines in the programming community:
  • Favor full words instead of abbreviations. For example, health instead of hp.
  • For booleans, use prefixes to differentiate them from other variable types, like do_*, has_*, or is_*. For example, is_alive, has_key, do_jump. Avoid negations like not_dead.
  • For functions, use verbs, like move(), jump(), shoot().
  • For signals, use verbs in the past tense, like died, pressed, opened, or took_hit.
  • Adopt the naming conventions of the language. In GDScript, pseudo-private variables and methods start with an underscore (_, like _process()), and constants are in all caps. For example, _health, MAX_HEALTH. Class names are in what we call PascalCase

    PascalCase

    Pascal, an old programming language, introduced this convention. All words start with a capital letter, including the first one. This is opposed to what we commonly call camelCase, where all words start with a capital letter, except the first.
    , like PlayerCharacter. Everyone adopting the same conventions will make it easier for you to read other people's code and adopt it.

Personal preferences

The following suggestions are more personal preferences.
  • If a function has an important condition, consider including it in the name. If for example, your move() method would not trigger in certain cases, consider including it in the name: move_or_stop(). If your load() method creates a save file when none is found, maybe call it load_or_create(). If you need to express too many branches, consider splitting the function into multiple functions, like load() and create().
  • Name parameters depending on their role in the function, not their type. For example, load_or_create(file_path: String), and not load_or_create(text: String).
  • Suffix arrays with *_list or *_collection. Plurals can be difficult to differentiate from singulars, and it's not always clear if a variable is an array or a single value. For example, if your list of items is called items, you could make a typo and write item instead of items. These one-letter differences are hard to spot. Better names are: enemies_list or items_collection.
  • In Godot specifically, consider making variable names unique in the project. This way, you can effortlessly search for and replace it throughout the project. For example, health might be used in many nodes, mobs and the player alike, so finding where you use the player health in the project would be hard. But player_health is unique to the player, and is easier to search.
  • Favor grouping code into larger functions over splitting systematically. In some software development industries, people tend to extract code into really small functions, but I find that it can make the code harder to read because of indirection: . I prefer to keep the code inline so it can be read like a book, top to bottom. If splitting the code is necessary for comprehension, you can add comments between the sections. For example, let's say you had a function like this:
    func check_crafting_requirements():
    	verify_available_items()
    	verify_available_skill()
    	verify_available_money()
    
    You would have to jump around the file to understand the full function. Instead, you can write:
    func check_crafting_requirements():
    	# Comment the first piece of functionality
    	for item in items:
    		# ...
    	if player.skills.crafting > 5:
    		# ...
    	# Comment another piece of functionality
    	# ...
    
    That way, the function can be read top to bottom, like a text.
  • Name your temporary variables, such as iterators, appropriately. For example, for item_index in items: is better than for i in items:. It's easier to understand what item_index is than i; if you need to rename it later, you can search and replace it, whereas it's hard to search for i.

Self documenting methodology

As I said above, you may not get the names right from the start. That's expected, so don't sweat it. Write the best name you think works at this point. Sometimes, I even name a variable temp because I don't know what I will use it for yet. Then, as I use it, I rename it to something more descriptive.
When you're done with a piece of functionality, whether a single function, an entire script, or a set of multiple scripts working together, wait a day or two so you forget it, and return to it to re-read it with fresh eyes. Make sure all the names are clear and accurate.
When your code works as you like, go through it and make sure to rewrite it the most straightforward way possible. Do not attempt to make it short, nor should you try to "optimize it" before you notice a problem. Make sure you write it in a detailed, deliberately clear manner.
Once your code is as self-documenting as possible, it's time to add comments.

What to document

You can never write too much documentation, so feel free to always write everything you think may be useful. But the most important thing to capture is the following:
  • any unclear part of the code. For example, in the module we just finished, the number -1 was a magical number that would quit the game. Documenting such things is essential.
  • any additional context that is not clear from the code. For example, if you're only supposed to call a function after the scene is ready.
  • If a property has any specific needs that the type system cannot enforce. For example, the scene you wrote in this module cannot work if dialogue_items is empty.
  • Any magical side effect, especially invisible effects like a property with a setter that changes something else. For example, in an online game, changing the username might refresh the connection to the server.
  • If a function is expensive for the CPU. For example, if a function loops over thousands of elements, you might write to be careful and to not to use it on every frame.
Good documentation might also reference other related pieces of code. For example, the documentation for show_text() might also point you to create_buttons(), which is used internally by the function.
What if my function really doesn't need documentation?
One interesting bit of online controversy is about documenting a function that is already entirely self-documented and requires nothing. The classical example is:
## Adds two numbers
func add(a: int, b: int) -> int:
	return a + b
In this example, some people would say the comment brings no additional information. But I'm afraid I have to disagree with this generic principle for four reasons:
  1. The idea that comments add nothing is relative.
  2. Consistency is important.
  3. Long functions can be hard to understand without comments.
  4. Documenting can reveal clarity mistakes.
Firstly, the fact that the comment brings "nothing additional" is relative. If someone is starting and has never seen a function, the comment will help a lot. It's like learning a new human language: beginners will need translation notes on each word, and other people only need translations on the more unusual words. No matter how experienced a programmer is, they can always find a piece of code that is hard to decipher for them, but easy for someone else. A useless comment to one person might be helpful to someone else.
Secondly, if you commented on all or most of your functions, leaving one out feels strange. I don't know if it bothers you, but it sure bothers me. If only for the consistency, I add some documentation anyway.
Thirdly, say a function is simple to understand in theory, but is very long. You'd have to read it all to know what it does. Without comments, you can't know if the person who wrote it forgot to comment it, or if the function doesn't need comments. The simple fact of having comments proves to yourself or to anyone reading that you did remember to document this function. It kind of acts as a "I did my job" marker.
Finally, documenting can often reveal subtle clarity mistakes. When explaining something in words, you will frequently realize you were using the wrong name for a variable.
For those reasons, I document all functions and variables, no matter how obvious they seem. You do what you want! I am not saying you should, or shouldn't do anything. But don't let anyone tell you that doing such a thing is stupid or useless. If it's helpful to you, then it's not meaningless.
Now, that we have understood self-documenting code, and what to write in comment, let's check out the syntax and style of comments in Godot.

Anatomy of a docstring

A "Doscstring" is a bit of code before a function or a variable that documents it. In Godot, it gets written with two initial ##.
## Adds two numbers
func add(a: int, b: int) -> int:
	return a + b
You can create as many line breaks as you want, but they will not count. The below will render on a single line:
## Adds
## two
## numbers
func add(a: int, b: int) -> int:
	return a + b
To get an actual linebreak, use [br]. Yes, that is BBCode.
You can find the complete list of codes in the official documentation, but let's look at a few interesting ones.
[param x] formats the parameter named x. For example:
## Adds two numbers: [br]
## [param a] and [param b]. Returns the addition of the two numbers
func add(a: int, b: int) -> int:
	return a + b
[member x] creates a link to the member named x, and [code] allows to enclose code examples.
You can use the regular ## before the class_name line to document the class generally. Write one short line, then skip a line, and write a more detailed description. The first line will be used a short class description.
@icon('./health_component_example.svg')
## A component that can be added to any node to give it a health property.
## 
## The Health component is a convenient quick shortcut for any unit or player
## to obtain the ability of having health and getting damage.
## [br]
## Has two main function, [member heal] and [member take_damage]. When 
## [member health] reaches zero, the class emits [signal has_died].
## [br]
##
## This class does nothing on its own, it expects another node to connect to the
## signal and react.
class_name HealthComponentExample extends Area2D

## Represents the entity's life. When it reaches 0, the entity is considered 
## dead
@export var health := 10

## When healing, health will not go over this amount.
@export var health_max := 10

## Becomes true when health reaches 0
var is_dead := false

## Emitted when health reaches 0
signal has_died


## Increments [member health].
## [param healing_amount]: should be a positive integer.
## To remove health point, use [member take_damage]
func heal(healing_amount: int) -> void:
	health += healing_amount
	if health > health_max:
		health = health_max


## Decrements [member health].
## [param damage_amount]: should be a positive integer.
## To increase health point, use [member heal]. [br]
## If health reaches zero, [member is_dead] will be set to [code]true[/code]
## and the [signal has_died] will be dispatched.
func take_damage(damage_amount: int) -> void:
	health -= damage_amount
	if health <= 0:
		health = 0
		is_dead = true
		has_died.emit()
It is common to keep lines under 80 characters. This is an old habit from a time when screens were smaller and scrolling to the side fastidious. Godot even draws a helpful vertical line at the 80 characters mark.
Now that you've checked how to write comments, let's see how we can read them.

Checking out the generated page

There's an example of documented file for you to use in reslessons_referencedocumentation_samplehealth_component_example.gd. You can open it in the editor to see the comments. Then, open the embedded documentation, and search for HealthComponentExample.
You can see the generated code directly in the code browser:
Just like the internal Godot documentation, this is extremely helpful to know how to use your custom code. It's also very neat looking!

Advanced: generate external documentation

Warning: very advanced things ahead, but I know some of you will be interested.
This section teaches you how to render basic webpages for your documentation. If you want to use the documentation outside the editor, it's possible. But it's not easy; if you're not used to the command line or HTML, the below might be a bit obscure. Feel free to ask clarifying questions in the comments!
You need to:
  • Use Godot from the command line to generate a set of data files
  • Read those data files with some script you write, and generate any output format you want, like HTML or PDF. In this example, I'll output HTML.
Godot can be used from the command line. For example:
godot --help
Will show you all the command line options that you can use. Instead of godot, substitute the location of the executable. For example, on Windows, if you had godot in a folder called "godot" in the root of the c: drive, you might do:
C:\godot\Godot_v4.0.3-stable_win64.exe --help
Once you're able to run godot from the command line, you can open your project's directory, and run:
godot --doctool ./docs --gdscript-docs res://lessons_reference/documentation_sample/
This writes the output to the docs directory (it will be created if it doesn't exist) and will read all the files starting from the lessons_reference/documentation_sample directory.
This outputs a set of XML files. XML is a text data format, made to be readable by humans, but easy for computers to parse. The XML generated by Godot looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<class name="HealthComponentExample" inherits="Area2D">
   <brief_description>
  A component that can be added to any node to give it a health property. ...
   </brief_description>
   <description>
   The Health component is a convenient quick shortcut...
   </description>
   <tutorials>
   </tutorials>
   <methods>
     <method name="heal">
       <return type="void" />
       <param index="0" name="healing_amount" type="int" />
       <description>
  Increments [member health]. [param healing_amount]: ...
       </description>
     </method>
   <members>
     <member name="health" type="int" setter="" getter="" default="10">
  Represents the entity's life. When it reaches 0, the entity is considered dead
     </member>
</class>
Of course, you cannot use XML directly, but many programming languages can parse XML. There is no readymade tool to transform this XML configuration into a webpage or a PDF, but you can write a script to read the XML, extract the data, and write it to an HTML page.
Can you show me how?
This is probably really out of scope for this course, but since there aren't any examples online, I didn't want to leave you high and dry. Here is a very simple script to convert the XML into a webpage. It assumes you ran the line of code above to generate the XML first. It only converts the example file provided, to keep the code as simple as possible. You can adapt it to convert entire directories if you want.
After the script below runs, an html page will be stored inside the res://docs directory. Godot will not show this directory by default (it hides all files it can't read), so you'll have to access it from your operating system's file browser.
You'll find this generation script in reslessons_referencedocumentation_samplegenerate_html_page.gd
## This script reads the documentation outputted by Godot and generates an HTML page
## This is an editor script: open it in the Godot editor, and select `File > Run`
## or press ctrl+shift+x to run it.
@tool
extends EditorScript

var component_file_name := "HealthComponentExample"

func _run():
	var input_file_name := "res://docs/"+component_file_name+".xml"
	var output_file_name := "res://docs/"+component_file_name+".html"
	# Create an XML Parser
	var xml := XMLParser.new()
	var success := xml.open(input_file_name)
	
	if success != OK:
		push_error("Couldn't open the XML file, did you forget to run the documentation generator?")
		return
	
	# Start generating the HTML
	var html_string := "<html><title>Documentation</title></html><body>"
	
	# Read the entire file
	while xml.read() == OK:
		if xml.get_node_type() == XMLParser.NODE_ELEMENT:
			var node_name := xml.get_node_name()
			match node_name:
				"class":
					var script_class_name := xml.get_named_attribute_value("name")
					var class_inherits := xml.get_named_attribute_value("inherits")
					html_string += "<h1>Class: %s</h1>"%[script_class_name]
					html_string += "<h2>Inherits: %s</h2>"%[class_inherits]
				"brief_description":
					xml.read() # Move to content
					var brief_description = xml.get_node_data().strip_edges()
					html_string += "<p><strong>Brief Description:</strong> %s</p>"%[brief_description]
				"method":
					var method_name = xml.get_named_attribute_value("name")
					html_string += "<hr/><h3>Method: %s</h3>"%[method_name]
				"return":
					var return_type := xml.get_named_attribute_value("type")
					html_string += "<p><strong>Return Type:</strong> %s</p>"%[return_type]
				"param":
					var param_name := xml.get_named_attribute_value("name")
					var param_type := xml.get_named_attribute_value("type")
					html_string += "<p><strong>Parameter:</strong> %s (%s)</p>"%[param_name, param_type]
				"description":
					xml.read() # Move to content
					if xml.get_node_type() == XMLParser.NODE_TEXT and not xml.is_empty():
						var description := xml.get_node_data().strip_edges()
						html_string += "<p><strong>Description:</strong> %s</p>"%[description]
				"member":
					var member_name := xml.get_named_attribute_value("name")
					var member_type := xml.get_named_attribute_value("type")
					var member_default := xml.get_named_attribute_value("default")
					xml.read() # Move to content
					var member_description = xml.get_node_data().strip_edges()
					html_string += "<hr/><h3>Member: %s</h3>"%[member_name]
					html_string += "<p><strong>Type:</strong> %s</p>"%[member_type]
					html_string += "<p><strong>Default Value:</strong> %s</p>"%[member_default]
					html_string += "<p><strong>Description:</strong> %s</p>"%[member_description]
				"signal":
					var member_name := xml.get_named_attribute_value("name")
					xml.read() # Move to content
					var member_description := xml.get_node_data().strip_edges()
					html_string += "<hr/><h3>Signal: %s</h3>"%[member_name]
					html_string += "<p><strong>Description:</strong> %s</p>"%[member_description]
	
	# Close the html string
	html_string += "</body></html>"
	
	# Write to an output file
	var output_file := FileAccess.open(output_file_name, FileAccess.WRITE)
	if output_file != null:
		output_file.store_string(html_string)
		output_file.close()
		print("HTML file generated successfully!")
	else:
		push_error("Failed to write HTML file!")
If you don't understand it... it's more than normal. This is several times more complicated than anything we've done so far. But feel free to ask questions in comments or on the Discord server!

Conclusion

This concludes the documentation study guide! I hope it was informative and convinced you to write the clearest code possible, and to document as much as you can. It's a great habit to have, and it will make your life easier in the long run.

Lesson Q&A

Use this space for questions related to what you're learning. For any other type of support (website, learning platform, payments, etc...) please get in touch using the contact form.

  • Opening the Command LinedyldoHi, I am a little confused on how to open the command line for my project. Also, does Godot support committing files to GitHub within the terminal? I would hate having to manually upload my entire folder to my repo for every change. 3 0 Jul. 28, 2024
  • Why don't you recommend identifying private methods with underscore?SingleMom420I've been doing this on my project and find it quite helpful for further specifying what functions should/should not be called by other scripts. Is there a reason you don't recommend it at GDQuest? 3 0 Jul. 26, 2024
Site is in BETA!found a bug?