Documenting your code
- 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.
##
) 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
- The documentation lives next to the code. It's easier to check that the two are in sync.
- 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.
- You can quickly write the documentation right after writing a script, so the code is still fresh in your mind.
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: 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:
- 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.
- 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!
Self-documenting code
health
but then realize you can acquire temporary health boosts, so the variable should be called base_health
.General suggestions
- Favor full words instead of abbreviations. For example,
health
instead ofhp
. - For booleans, use prefixes to differentiate them from other variable types, like
do_*
,has_*
, oris_*
. For example,is_alive
,has_key
,do_jump
. Avoid negations likenot_dead
. - For functions, use verbs, like
move()
,jump()
,shoot()
. - For signals, use verbs in the past tense, like
died
,pressed
,opened
, ortook_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, likePascalCase
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.PlayerCharacter
. Everyone adopting the same conventions will make it easier for you to read other people's code and adopt it.
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 yourload()
method creates a save file when none is found, maybe call itload_or_create()
. If you need to express too many branches, consider splitting the function into multiple functions, likeload()
andcreate()
. - Name parameters depending on their role in the function, not their type. For example,
load_or_create(file_path: String)
, and notload_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 calleditems
, you could make a typo and writeitem
instead ofitems
. These one-letter differences are hard to spot. Better names are:enemies_list
oritems_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. Butplayer_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:
You would have to jump around the file to understand the full function. Instead, you can write:func check_crafting_requirements(): verify_available_items() verify_available_skill() verify_available_money()
That way, the function can be read top to bottom, like a text.func check_crafting_requirements(): # Comment the first piece of functionality for item in items: # ... if player.skills.crafting > 5: # ... # Comment another piece of functionality # ...
- Name your temporary variables, such as iterators, appropriately. For example,
for item_index in items:
is better thanfor i in items:
. It's easier to understand whatitem_index
is thani
; if you need to rename it later, you can search and replace it, whereas it's hard to search fori
.
Self documenting methodology
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.What to document
- 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.
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: 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: 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.
## Adds two numbers
func add(a: int, b: int) -> int:
return a + b
- The idea that comments add nothing is relative.
- Consistency is important.
- Long functions can be hard to understand without comments.
- Documenting can reveal clarity mistakes.
Anatomy of a docstring
##
.## Adds two numbers
func add(a: int, b: int) -> int:
return a + b
## Adds
## two
## numbers
func add(a: int, b: int) -> int:
return a + b
[br]
. Yes, that is BBCode.[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.##
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()
Checking out the generated page
HealthComponentExample
.Advanced: generate external documentation
- 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 --help
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
godot --doctool ./docs --gdscript-docs res://lessons_reference/documentation_sample/
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.<?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>
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 res lessons_reference documentation_sample generate_html_page.gd 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!
## 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!")
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.